Upgrade CakePHP from 2.2.5 to 2.9.5

This commit is contained in:
Brm Ko 2017-02-26 15:29:44 +01:00
parent 5a580df460
commit 235a541597
793 changed files with 60746 additions and 23753 deletions

View file

@ -4,19 +4,18 @@
*
* Methods to make numbers more readable.
*
* PHP 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Utility
* @since CakePHP(tm) v 0.10.0.1076
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
@ -30,24 +29,42 @@
class CakeNumber {
/**
* Currencies supported by the helper. You can add additional currency formats
* Currencies supported by the helper. You can add additional currency formats
* with CakeNumber::addFormat
*
* @var array
*/
protected static $_currencies = array(
'AUD' => array(
'wholeSymbol' => '$', 'wholePosition' => 'before', 'fractionSymbol' => 'c', 'fractionPosition' => 'after',
'zero' => 0, 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()', 'escape' => true,
'fractionExponent' => 2
),
'CAD' => array(
'wholeSymbol' => '$', 'wholePosition' => 'before', 'fractionSymbol' => 'c', 'fractionPosition' => 'after',
'zero' => 0, 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()', 'escape' => true,
'fractionExponent' => 2
),
'USD' => array(
'wholeSymbol' => '$', 'wholePosition' => 'before', 'fractionSymbol' => 'c', 'fractionPosition' => 'after',
'zero' => 0, 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()', 'escape' => true
),
'GBP' => array(
'wholeSymbol' => '£', 'wholePosition' => 'before', 'fractionSymbol' => 'p', 'fractionPosition' => 'after',
'zero' => 0, 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()','escape' => false
'zero' => 0, 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()', 'escape' => true,
'fractionExponent' => 2
),
'EUR' => array(
'wholeSymbol' => '€', 'wholePosition' => 'before', 'fractionSymbol' => false, 'fractionPosition' => 'after',
'zero' => 0, 'places' => 2, 'thousands' => '.', 'decimals' => ',', 'negative' => '()', 'escape' => false
)
'wholeSymbol' => '€', 'wholePosition' => 'before', 'fractionSymbol' => false, 'fractionPosition' => 'after',
'zero' => 0, 'places' => 2, 'thousands' => '.', 'decimals' => ',', 'negative' => '()', 'escape' => true,
'fractionExponent' => 0
),
'GBP' => array(
'wholeSymbol' => '£', 'wholePosition' => 'before', 'fractionSymbol' => 'p', 'fractionPosition' => 'after',
'zero' => 0, 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()', 'escape' => true,
'fractionExponent' => 2
),
'JPY' => array(
'wholeSymbol' => '¥', 'wholePosition' => 'before', 'fractionSymbol' => false, 'fractionPosition' => 'after',
'zero' => 0, 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()', 'escape' => true,
'fractionExponent' => 0
),
);
/**
@ -56,33 +73,41 @@ class CakeNumber {
* @var array
*/
protected static $_currencyDefaults = array(
'wholeSymbol' => '', 'wholePosition' => 'before', 'fractionSymbol' => '', 'fractionPosition' => 'after',
'zero' => '0', 'places' => 2, 'thousands' => ',', 'decimals' => '.','negative' => '()', 'escape' => true,
'wholeSymbol' => '', 'wholePosition' => 'before', 'fractionSymbol' => false, 'fractionPosition' => 'after',
'zero' => '0', 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()', 'escape' => true,
'fractionExponent' => 2
);
/**
* Default currency used by CakeNumber::currency()
*
* @var string
*/
protected static $_defaultCurrency = 'USD';
/**
* If native number_format() should be used. If >= PHP5.4
*
* @var boolean
* @var bool
*/
protected static $_numberFormatSupport = null;
/**
* Formats a number with a level of precision.
*
* @param float $number A floating point number.
* @param integer $precision The precision of the returned number.
* @param float $value A floating point number.
* @param int $precision The precision of the returned number.
* @return float Formatted float.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::precision
*/
public static function precision($number, $precision = 3) {
return sprintf("%01.{$precision}F", $number);
public static function precision($value, $precision = 3) {
return sprintf("%01.{$precision}f", $value);
}
/**
* Returns a formatted-for-humans file size.
*
* @param integer $size Size in bytes
* @param int $size Size in bytes
* @return string Human readable size
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::toReadableSize
*/
@ -91,38 +116,84 @@ class CakeNumber {
case $size < 1024:
return __dn('cake', '%d Byte', '%d Bytes', $size, $size);
case round($size / 1024) < 1024:
return __d('cake', '%d KB', self::precision($size / 1024, 0));
return __d('cake', '%s KB', static::precision($size / 1024, 0));
case round($size / 1024 / 1024, 2) < 1024:
return __d('cake', '%.2f MB', self::precision($size / 1024 / 1024, 2));
return __d('cake', '%s MB', static::precision($size / 1024 / 1024, 2));
case round($size / 1024 / 1024 / 1024, 2) < 1024:
return __d('cake', '%.2f GB', self::precision($size / 1024 / 1024 / 1024, 2));
return __d('cake', '%s GB', static::precision($size / 1024 / 1024 / 1024, 2));
default:
return __d('cake', '%.2f TB', self::precision($size / 1024 / 1024 / 1024 / 1024, 2));
return __d('cake', '%s TB', static::precision($size / 1024 / 1024 / 1024 / 1024, 2));
}
}
/**
* Converts filesize from human readable string to bytes
*
* @param string $size Size in human readable string like '5MB', '5M', '500B', '50kb' etc.
* @param mixed $default Value to be returned when invalid size was used, for example 'Unknown type'
* @return mixed Number of bytes as integer on success, `$default` on failure if not false
* @throws CakeException On invalid Unit type.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::fromReadableSize
*/
public static function fromReadableSize($size, $default = false) {
if (ctype_digit($size)) {
return (int)$size;
}
$size = strtoupper($size);
$l = -2;
$i = array_search(substr($size, -2), array('KB', 'MB', 'GB', 'TB', 'PB'));
if ($i === false) {
$l = -1;
$i = array_search(substr($size, -1), array('K', 'M', 'G', 'T', 'P'));
}
if ($i !== false) {
$size = substr($size, 0, $l);
return $size * pow(1024, $i + 1);
}
if (substr($size, -1) === 'B' && ctype_digit(substr($size, 0, -1))) {
$size = substr($size, 0, -1);
return (int)$size;
}
if ($default !== false) {
return $default;
}
throw new CakeException(__d('cake_dev', 'No unit type.'));
}
/**
* Formats a number into a percentage string.
*
* @param float $number A floating point number
* @param integer $precision The precision of the returned number
* Options:
*
* - `multiply`: Multiply the input value by 100 for decimal percentages.
*
* @param float $value A floating point number
* @param int $precision The precision of the returned number
* @param array $options Options
* @return string Percentage string
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::toPercentage
*/
public static function toPercentage($number, $precision = 2) {
return self::precision($number, $precision) . '%';
public static function toPercentage($value, $precision = 2, $options = array()) {
$options += array('multiply' => false);
if ($options['multiply']) {
$value *= 100;
}
return static::precision($value, $precision) . '%';
}
/**
* Formats a number into a currency format.
*
* @param float $number A floating point number
* @param integer $options if int then places, if string then before, if (,.-) then use it
* @param float $value A floating point number
* @param int $options If integer then places, if string then before, if (,.-) then use it
* or array with places and before keys
* @return string formatted number
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::format
*/
public static function format($number, $options = false) {
public static function format($value, $options = false) {
$places = 0;
if (is_int($options)) {
$places = $options;
@ -145,11 +216,13 @@ class CakeNumber {
$escape = true;
if (is_array($options)) {
$options = array_merge(array('before' => '$', 'places' => 2, 'thousands' => ',', 'decimals' => '.'), $options);
$defaults = array('before' => '$', 'places' => 2, 'thousands' => ',', 'decimals' => '.');
$options += $defaults;
extract($options);
}
$out = $before . self::_numberFormat($number, $places, $decimals, $thousands) . $after;
$value = static::_numberFormat($value, $places, '.', '');
$out = $before . static::_numberFormat($value, $places, $decimals, $thousands) . $after;
if ($escape) {
return h($out);
@ -157,34 +230,59 @@ class CakeNumber {
return $out;
}
/**
* Formats a number into a currency format to show deltas (signed differences in value).
*
* ### Options
*
* - `places` - Number of decimal places to use. ie. 2
* - `fractionExponent` - Fraction exponent of this specific currency. Defaults to 2.
* - `before` - The string to place before whole numbers. ie. '['
* - `after` - The string to place after decimal numbers. ie. ']'
* - `thousands` - Thousands separator ie. ','
* - `decimals` - Decimal separator symbol ie. '.'
*
* @param float $value A floating point number
* @param array $options Options list.
* @return string formatted delta
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::formatDelta
*/
public static function formatDelta($value, $options = array()) {
$places = isset($options['places']) ? $options['places'] : 0;
$value = static::_numberFormat($value, $places, '.', '');
$sign = $value > 0 ? '+' : '';
$options['before'] = isset($options['before']) ? $options['before'] . $sign : $sign;
return static::format($value, $options);
}
/**
* Alternative number_format() to accommodate multibyte decimals and thousands < PHP 5.4
*
* @param float $number
* @param integer $places
* @param string $decimals
* @param string $thousands
* @param float $value Value to format.
* @param int $places Decimal places to use.
* @param string $decimals Decimal position string.
* @param string $thousands Thousands separator string.
* @return string
*/
protected static function _numberFormat($number, $places = 0, $decimals = '.', $thousands = ',') {
if (!isset(self::$_numberFormatSupport)) {
self::$_numberFormatSupport = version_compare(PHP_VERSION, '5.4.0', '>=');
protected static function _numberFormat($value, $places = 0, $decimals = '.', $thousands = ',') {
if (!isset(static::$_numberFormatSupport)) {
static::$_numberFormatSupport = version_compare(PHP_VERSION, '5.4.0', '>=');
}
if (self::$_numberFormatSupport) {
return number_format($number, $places, $decimals, $thousands);
if (static::$_numberFormatSupport) {
return number_format($value, $places, $decimals, $thousands);
}
$number = number_format($number, $places, '.', '');
$value = number_format($value, $places, '.', '');
$after = '';
$foundDecimal = strpos($number, '.');
$foundDecimal = strpos($value, '.');
if ($foundDecimal !== false) {
$after = substr($number, $foundDecimal);
$number = substr($number, 0, $foundDecimal);
$after = substr($value, $foundDecimal);
$value = substr($value, 0, $foundDecimal);
}
while (($foundThousand = preg_replace('/(\d+)(\d\d\d)/', '\1 \2', $number)) != $number) {
$number = $foundThousand;
while (($foundThousand = preg_replace('/(\d+)(\d\d\d)/', '\1 \2', $value)) !== $value) {
$value = $foundThousand;
}
$number .= $after;
return strtr($number, array(' ' => $thousands, '.' => $decimals));
$value .= $after;
return strtr($value, array(' ' => $thousands, '.' => $decimals));
}
/**
@ -203,36 +301,40 @@ class CakeNumber {
* ie. '$'. `before` is an alias for `wholeSymbol`.
* - `after` - The currency symbol to place after decimal numbers
* ie. 'c'. Set to boolean false to use no decimal symbol.
* eg. 0.35 => $0.35. `after` is an alias for `fractionSymbol`
* eg. 0.35 => $0.35. `after` is an alias for `fractionSymbol`
* - `zero` - The text to use for zero values, can be a
* string or a number. ie. 0, 'Free!'
* - `places` - Number of decimal places to use. ie. 2
* - `fractionExponent` - Fraction exponent of this specific currency. Defaults to 2.
* - `thousands` - Thousands separator ie. ','
* - `decimals` - Decimal separator symbol ie. '.'
* - `negative` - Symbol for negative numbers. If equal to '()',
* the number will be wrapped with ( and )
* - `escape` - Should the output be escaped for html special characters.
* The default value for this option is controlled by the currency settings.
* By default the EUR, and GBP contain HTML encoded symbols. If you require non HTML
* encoded symbols you will need to update the settings with the correct bytes.
* By default all currencies contain utf-8 symbols and don't need this changed. If you require
* non HTML encoded symbols you will need to update the settings with the correct bytes.
*
* @param float $number
* @param float $value Value to format.
* @param string $currency Shortcut to default options. Valid values are
* 'USD', 'EUR', 'GBP', otherwise set at least 'before' and 'after' options.
* @param array $options
* @param array $options Options list.
* @return string Number formatted as a currency.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::currency
*/
public static function currency($number, $currency = 'USD', $options = array()) {
$default = self::$_currencyDefaults;
public static function currency($value, $currency = null, $options = array()) {
$defaults = static::$_currencyDefaults;
if ($currency === null) {
$currency = static::defaultCurrency();
}
if (isset(self::$_currencies[$currency])) {
$default = self::$_currencies[$currency];
if (isset(static::$_currencies[$currency])) {
$defaults = static::$_currencies[$currency];
} elseif (is_string($currency)) {
$options['before'] = $currency;
}
$options = array_merge($default, $options);
$options += $defaults;
if (isset($options['before']) && $options['before'] !== '') {
$options['wholeSymbol'] = $options['before'];
@ -244,27 +346,28 @@ class CakeNumber {
$result = $options['before'] = $options['after'] = null;
$symbolKey = 'whole';
if ($number == 0 ) {
if ($options['zero'] !== 0 ) {
$value = (float)$value;
if (!$value) {
if ($options['zero'] !== 0) {
return $options['zero'];
}
} elseif ($number < 1 && $number > -1 ) {
} elseif ($value < 1 && $value > -1) {
if ($options['fractionSymbol'] !== false) {
$multiply = intval('1' . str_pad('', $options['places'], '0'));
$number = $number * $multiply;
$multiply = pow(10, $options['fractionExponent']);
$value = $value * $multiply;
$options['places'] = null;
$symbolKey = 'fraction';
}
}
$position = $options[$symbolKey . 'Position'] != 'after' ? 'before' : 'after';
$position = $options[$symbolKey . 'Position'] !== 'after' ? 'before' : 'after';
$options[$position] = $options[$symbolKey . 'Symbol'];
$abs = abs($number);
$result = self::format($abs, $options);
$abs = abs($value);
$result = static::format($abs, $options);
if ($number < 0 ) {
if ($options['negative'] == '()') {
if ($value < 0) {
if ($options['negative'] === '()') {
$result = '(' . $result . ')';
} else {
$result = $options['negative'] . $result;
@ -274,14 +377,14 @@ class CakeNumber {
}
/**
* Add a currency format to the Number helper. Makes reusing
* Add a currency format to the Number helper. Makes reusing
* currency formats easier.
*
* {{{ $number->addFormat('NOK', array('before' => 'Kr. ')); }}}
* ``` $number->addFormat('NOK', array('before' => 'Kr. ')); ```
*
* You can now use `NOK` as a shortform when formatting currency amounts.
*
* {{{ $number->currency($value, 'NOK'); }}}
* ``` $number->currency($value, 'NOK'); ```
*
* Added formats are merged with the defaults defined in CakeNumber::$_currencyDefaults
* See CakeNumber::currency() for more information on the various options and their function.
@ -293,7 +396,21 @@ class CakeNumber {
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::addFormat
*/
public static function addFormat($formatName, $options) {
self::$_currencies[$formatName] = $options + self::$_currencyDefaults;
static::$_currencies[$formatName] = $options + static::$_currencyDefaults;
}
/**
* Getter/setter for default currency
*
* @param string $currency Default currency string used by currency() if $currency argument is not provided
* @return string Currency
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::defaultCurrency
*/
public static function defaultCurrency($currency = null) {
if ($currency) {
static::$_defaultCurrency = $currency;
}
return static::$_defaultCurrency;
}
}

View file

@ -0,0 +1,710 @@
<?php
/**
* String handling methods.
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Utility
* @since CakePHP(tm) v 1.2.0.5551
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* String handling methods.
*
* @package Cake.Utility
*/
class CakeText {
/**
* Generate a random UUID
*
* @see http://www.ietf.org/rfc/rfc4122.txt
* @return string RFC 4122 UUID
*/
public static function uuid() {
$node = env('SERVER_ADDR');
if (strpos($node, ':') !== false) {
if (substr_count($node, '::')) {
$node = str_replace(
'::', str_repeat(':0000', 8 - substr_count($node, ':')) . ':', $node
);
}
$node = explode(':', $node);
$ipSix = '';
foreach ($node as $id) {
$ipSix .= str_pad(base_convert($id, 16, 2), 16, 0, STR_PAD_LEFT);
}
$node = base_convert($ipSix, 2, 10);
if (strlen($node) < 38) {
$node = null;
} else {
$node = crc32($node);
}
} elseif (empty($node)) {
$host = env('HOSTNAME');
if (empty($host)) {
$host = env('HOST');
}
if (!empty($host)) {
$ip = gethostbyname($host);
if ($ip === $host) {
$node = crc32($host);
} else {
$node = ip2long($ip);
}
}
} elseif ($node !== '127.0.0.1') {
$node = ip2long($node);
} else {
$node = null;
}
if (empty($node)) {
$node = crc32(Configure::read('Security.salt'));
}
if (function_exists('hphp_get_thread_id')) {
$pid = hphp_get_thread_id();
} elseif (function_exists('zend_thread_id')) {
$pid = zend_thread_id();
} else {
$pid = getmypid();
}
if (!$pid || $pid > 65535) {
$pid = mt_rand(0, 0xfff) | 0x4000;
}
list($timeMid, $timeLow) = explode(' ', microtime());
return sprintf(
"%08x-%04x-%04x-%02x%02x-%04x%08x", (int)$timeLow, (int)substr($timeMid, 2) & 0xffff,
mt_rand(0, 0xfff) | 0x4000, mt_rand(0, 0x3f) | 0x80, mt_rand(0, 0xff), $pid, $node
);
}
/**
* Tokenizes a string using $separator, ignoring any instance of $separator that appears between
* $leftBound and $rightBound.
*
* @param string $data The data to tokenize.
* @param string $separator The token to split the data on.
* @param string $leftBound The left boundary to ignore separators in.
* @param string $rightBound The right boundary to ignore separators in.
* @return mixed Array of tokens in $data or original input if empty.
*/
public static function tokenize($data, $separator = ',', $leftBound = '(', $rightBound = ')') {
if (empty($data)) {
return array();
}
$depth = 0;
$offset = 0;
$buffer = '';
$results = array();
$length = mb_strlen($data);
$open = false;
while ($offset <= $length) {
$tmpOffset = -1;
$offsets = array(
mb_strpos($data, $separator, $offset),
mb_strpos($data, $leftBound, $offset),
mb_strpos($data, $rightBound, $offset)
);
for ($i = 0; $i < 3; $i++) {
if ($offsets[$i] !== false && ($offsets[$i] < $tmpOffset || $tmpOffset == -1)) {
$tmpOffset = $offsets[$i];
}
}
if ($tmpOffset !== -1) {
$buffer .= mb_substr($data, $offset, ($tmpOffset - $offset));
$char = mb_substr($data, $tmpOffset, 1);
if (!$depth && $char === $separator) {
$results[] = $buffer;
$buffer = '';
} else {
$buffer .= $char;
}
if ($leftBound !== $rightBound) {
if ($char === $leftBound) {
$depth++;
}
if ($char === $rightBound) {
$depth--;
}
} else {
if ($char === $leftBound) {
if (!$open) {
$depth++;
$open = true;
} else {
$depth--;
}
}
}
$offset = ++$tmpOffset;
} else {
$results[] = $buffer . mb_substr($data, $offset);
$offset = $length + 1;
}
}
if (empty($results) && !empty($buffer)) {
$results[] = $buffer;
}
if (!empty($results)) {
return array_map('trim', $results);
}
return array();
}
/**
* Replaces variable placeholders inside a $str with any given $data. Each key in the $data array
* corresponds to a variable placeholder name in $str.
* Example: `CakeText::insert(':name is :age years old.', array('name' => 'Bob', '65'));`
* Returns: Bob is 65 years old.
*
* Available $options are:
*
* - before: The character or string in front of the name of the variable placeholder (Defaults to `:`)
* - after: The character or string after the name of the variable placeholder (Defaults to null)
* - escape: The character or string used to escape the before character / string (Defaults to `\`)
* - format: A regex to use for matching variable placeholders. Default is: `/(?<!\\)\:%s/`
* (Overwrites before, after, breaks escape / clean)
* - clean: A boolean or array with instructions for CakeText::cleanInsert
*
* @param string $str A string containing variable placeholders
* @param array $data A key => val array where each key stands for a placeholder variable name
* to be replaced with val
* @param array $options An array of options, see description above
* @return string
*/
public static function insert($str, $data, $options = array()) {
$defaults = array(
'before' => ':', 'after' => null, 'escape' => '\\', 'format' => null, 'clean' => false
);
$options += $defaults;
$format = $options['format'];
$data = (array)$data;
if (empty($data)) {
return ($options['clean']) ? CakeText::cleanInsert($str, $options) : $str;
}
if (!isset($format)) {
$format = sprintf(
'/(?<!%s)%s%%s%s/',
preg_quote($options['escape'], '/'),
str_replace('%', '%%', preg_quote($options['before'], '/')),
str_replace('%', '%%', preg_quote($options['after'], '/'))
);
}
if (strpos($str, '?') !== false && is_numeric(key($data))) {
$offset = 0;
while (($pos = strpos($str, '?', $offset)) !== false) {
$val = array_shift($data);
$offset = $pos + strlen($val);
$str = substr_replace($str, $val, $pos, 1);
}
return ($options['clean']) ? CakeText::cleanInsert($str, $options) : $str;
}
asort($data);
$dataKeys = array_keys($data);
$hashKeys = array_map('crc32', $dataKeys);
$tempData = array_combine($dataKeys, $hashKeys);
krsort($tempData);
foreach ($tempData as $key => $hashVal) {
$key = sprintf($format, preg_quote($key, '/'));
$str = preg_replace($key, $hashVal, $str);
}
$dataReplacements = array_combine($hashKeys, array_values($data));
foreach ($dataReplacements as $tmpHash => $tmpValue) {
$tmpValue = (is_array($tmpValue)) ? '' : $tmpValue;
$str = str_replace($tmpHash, $tmpValue, $str);
}
if (!isset($options['format']) && isset($options['before'])) {
$str = str_replace($options['escape'] . $options['before'], $options['before'], $str);
}
return ($options['clean']) ? CakeText::cleanInsert($str, $options) : $str;
}
/**
* Cleans up a CakeText::insert() formatted string with given $options depending on the 'clean' key in
* $options. The default method used is text but html is also available. The goal of this function
* is to replace all whitespace and unneeded markup around placeholders that did not get replaced
* by CakeText::insert().
*
* @param string $str CakeText to clean.
* @param array $options Options list.
* @return string
* @see CakeText::insert()
*/
public static function cleanInsert($str, $options) {
$clean = $options['clean'];
if (!$clean) {
return $str;
}
if ($clean === true) {
$clean = array('method' => 'text');
}
if (!is_array($clean)) {
$clean = array('method' => $options['clean']);
}
switch ($clean['method']) {
case 'html':
$clean = array_merge(array(
'word' => '[\w,.]+',
'andText' => true,
'replacement' => '',
), $clean);
$kleenex = sprintf(
'/[\s]*[a-z]+=(")(%s%s%s[\s]*)+\\1/i',
preg_quote($options['before'], '/'),
$clean['word'],
preg_quote($options['after'], '/')
);
$str = preg_replace($kleenex, $clean['replacement'], $str);
if ($clean['andText']) {
$options['clean'] = array('method' => 'text');
$str = CakeText::cleanInsert($str, $options);
}
break;
case 'text':
$clean = array_merge(array(
'word' => '[\w,.]+',
'gap' => '[\s]*(?:(?:and|or)[\s]*)?',
'replacement' => '',
), $clean);
$kleenex = sprintf(
'/(%s%s%s%s|%s%s%s%s)/',
preg_quote($options['before'], '/'),
$clean['word'],
preg_quote($options['after'], '/'),
$clean['gap'],
$clean['gap'],
preg_quote($options['before'], '/'),
$clean['word'],
preg_quote($options['after'], '/')
);
$str = preg_replace($kleenex, $clean['replacement'], $str);
break;
}
return $str;
}
/**
* Wraps text to a specific width, can optionally wrap at word breaks.
*
* ### Options
*
* - `width` The width to wrap to. Defaults to 72.
* - `wordWrap` Only wrap on words breaks (spaces) Defaults to true.
* - `indent` CakeText to indent with. Defaults to null.
* - `indentAt` 0 based index to start indenting at. Defaults to 0.
*
* @param string $text The text to format.
* @param array|int $options Array of options to use, or an integer to wrap the text to.
* @return string Formatted text.
*/
public static function wrap($text, $options = array()) {
if (is_numeric($options)) {
$options = array('width' => $options);
}
$options += array('width' => 72, 'wordWrap' => true, 'indent' => null, 'indentAt' => 0);
if ($options['wordWrap']) {
$wrapped = static::wordWrap($text, $options['width'], "\n");
} else {
$wrapped = trim(chunk_split($text, $options['width'] - 1, "\n"));
}
if (!empty($options['indent'])) {
$chunks = explode("\n", $wrapped);
for ($i = $options['indentAt'], $len = count($chunks); $i < $len; $i++) {
$chunks[$i] = $options['indent'] . $chunks[$i];
}
$wrapped = implode("\n", $chunks);
}
return $wrapped;
}
/**
* Unicode aware version of wordwrap.
*
* @param string $text The text to format.
* @param int $width The width to wrap to. Defaults to 72.
* @param string $break The line is broken using the optional break parameter. Defaults to '\n'.
* @param bool $cut If the cut is set to true, the string is always wrapped at the specified width.
* @return string Formatted text.
*/
public static function wordWrap($text, $width = 72, $break = "\n", $cut = false) {
$paragraphs = explode($break, $text);
foreach ($paragraphs as &$paragraph) {
$paragraph = static::_wordWrap($paragraph, $width, $break, $cut);
}
return implode($break, $paragraphs);
}
/**
* Helper method for wordWrap().
*
* @param string $text The text to format.
* @param int $width The width to wrap to. Defaults to 72.
* @param string $break The line is broken using the optional break parameter. Defaults to '\n'.
* @param bool $cut If the cut is set to true, the string is always wrapped at the specified width.
* @return string Formatted text.
*/
protected static function _wordWrap($text, $width = 72, $break = "\n", $cut = false) {
if ($cut) {
$parts = array();
while (mb_strlen($text) > 0) {
$part = mb_substr($text, 0, $width);
$parts[] = trim($part);
$text = trim(mb_substr($text, mb_strlen($part)));
}
return implode($break, $parts);
}
$parts = array();
while (mb_strlen($text) > 0) {
if ($width >= mb_strlen($text)) {
$parts[] = trim($text);
break;
}
$part = mb_substr($text, 0, $width);
$nextChar = mb_substr($text, $width, 1);
if ($nextChar !== ' ') {
$breakAt = mb_strrpos($part, ' ');
if ($breakAt === false) {
$breakAt = mb_strpos($text, ' ', $width);
}
if ($breakAt === false) {
$parts[] = trim($text);
break;
}
$part = mb_substr($text, 0, $breakAt);
}
$part = trim($part);
$parts[] = $part;
$text = trim(mb_substr($text, mb_strlen($part)));
}
return implode($break, $parts);
}
/**
* Highlights a given phrase in a text. You can specify any expression in highlighter that
* may include the \1 expression to include the $phrase found.
*
* ### Options:
*
* - `format` The piece of html with that the phrase will be highlighted
* - `html` If true, will ignore any HTML tags, ensuring that only the correct text is highlighted
* - `regex` a custom regex rule that is used to match words, default is '|$tag|iu'
*
* @param string $text Text to search the phrase in.
* @param string|array $phrase The phrase or phrases that will be searched.
* @param array $options An array of html attributes and options.
* @return string The highlighted text
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::highlight
*/
public static function highlight($text, $phrase, $options = array()) {
if (empty($phrase)) {
return $text;
}
$defaults = array(
'format' => '<span class="highlight">\1</span>',
'html' => false,
'regex' => "|%s|iu"
);
$options += $defaults;
extract($options);
if (is_array($phrase)) {
$replace = array();
$with = array();
foreach ($phrase as $key => $segment) {
$segment = '(' . preg_quote($segment, '|') . ')';
if ($html) {
$segment = "(?![^<]+>)$segment(?![^<]+>)";
}
$with[] = (is_array($format)) ? $format[$key] : $format;
$replace[] = sprintf($options['regex'], $segment);
}
return preg_replace($replace, $with, $text);
}
$phrase = '(' . preg_quote($phrase, '|') . ')';
if ($html) {
$phrase = "(?![^<]+>)$phrase(?![^<]+>)";
}
return preg_replace(sprintf($options['regex'], $phrase), $format, $text);
}
/**
* Strips given text of all links (<a href=....).
*
* @param string $text Text
* @return string The text without links
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::stripLinks
*/
public static function stripLinks($text) {
return preg_replace('|<a\s+[^>]+>|im', '', preg_replace('|<\/a>|im', '', $text));
}
/**
* Truncates text starting from the end.
*
* Cuts a string to the length of $length and replaces the first characters
* with the ellipsis if the text is longer than length.
*
* ### Options:
*
* - `ellipsis` Will be used as Beginning and prepended to the trimmed string
* - `exact` If false, $text will not be cut mid-word
*
* @param string $text CakeText to truncate.
* @param int $length Length of returned string, including ellipsis.
* @param array $options An array of options.
* @return string Trimmed string.
*/
public static function tail($text, $length = 100, $options = array()) {
$defaults = array(
'ellipsis' => '...', 'exact' => true
);
$options += $defaults;
extract($options);
if (!function_exists('mb_strlen')) {
class_exists('Multibyte');
}
if (mb_strlen($text) <= $length) {
return $text;
}
$truncate = mb_substr($text, mb_strlen($text) - $length + mb_strlen($ellipsis));
if (!$exact) {
$spacepos = mb_strpos($truncate, ' ');
$truncate = $spacepos === false ? '' : trim(mb_substr($truncate, $spacepos));
}
return $ellipsis . $truncate;
}
/**
* Truncates text.
*
* Cuts a string to the length of $length and replaces the last characters
* with the ellipsis if the text is longer than length.
*
* ### Options:
*
* - `ellipsis` Will be used as Ending and appended to the trimmed string (`ending` is deprecated)
* - `exact` If false, $text will not be cut mid-word
* - `html` If true, HTML tags would be handled correctly
*
* @param string $text CakeText to truncate.
* @param int $length Length of returned string, including ellipsis.
* @param array $options An array of html attributes and options.
* @return string Trimmed string.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::truncate
*/
public static function truncate($text, $length = 100, $options = array()) {
$defaults = array(
'ellipsis' => '...', 'exact' => true, 'html' => false
);
if (isset($options['ending'])) {
$defaults['ellipsis'] = $options['ending'];
} elseif (!empty($options['html']) && Configure::read('App.encoding') === 'UTF-8') {
$defaults['ellipsis'] = "\xe2\x80\xa6";
}
$options += $defaults;
extract($options);
if (!function_exists('mb_strlen')) {
class_exists('Multibyte');
}
if ($html) {
if (mb_strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
return $text;
}
$totalLength = mb_strlen(strip_tags($ellipsis));
$openTags = array();
$truncate = '';
preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER);
foreach ($tags as $tag) {
if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])) {
if (preg_match('/<[\w]+[^>]*>/s', $tag[0])) {
array_unshift($openTags, $tag[2]);
} elseif (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag)) {
$pos = array_search($closeTag[1], $openTags);
if ($pos !== false) {
array_splice($openTags, $pos, 1);
}
}
}
$truncate .= $tag[1];
$contentLength = mb_strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $tag[3]));
if ($contentLength + $totalLength > $length) {
$left = $length - $totalLength;
$entitiesLength = 0;
if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE)) {
foreach ($entities[0] as $entity) {
if ($entity[1] + 1 - $entitiesLength <= $left) {
$left--;
$entitiesLength += mb_strlen($entity[0]);
} else {
break;
}
}
}
$truncate .= mb_substr($tag[3], 0, $left + $entitiesLength);
break;
} else {
$truncate .= $tag[3];
$totalLength += $contentLength;
}
if ($totalLength >= $length) {
break;
}
}
} else {
if (mb_strlen($text) <= $length) {
return $text;
}
$truncate = mb_substr($text, 0, $length - mb_strlen($ellipsis));
}
if (!$exact) {
$spacepos = mb_strrpos($truncate, ' ');
if ($html) {
$truncateCheck = mb_substr($truncate, 0, $spacepos);
$lastOpenTag = mb_strrpos($truncateCheck, '<');
$lastCloseTag = mb_strrpos($truncateCheck, '>');
if ($lastOpenTag > $lastCloseTag) {
preg_match_all('/<[\w]+[^>]*>/s', $truncate, $lastTagMatches);
$lastTag = array_pop($lastTagMatches[0]);
$spacepos = mb_strrpos($truncate, $lastTag) + mb_strlen($lastTag);
}
$bits = mb_substr($truncate, $spacepos);
preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER);
if (!empty($droppedTags)) {
if (!empty($openTags)) {
foreach ($droppedTags as $closingTag) {
if (!in_array($closingTag[1], $openTags)) {
array_unshift($openTags, $closingTag[1]);
}
}
} else {
foreach ($droppedTags as $closingTag) {
$openTags[] = $closingTag[1];
}
}
}
}
$truncate = mb_substr($truncate, 0, $spacepos);
}
$truncate .= $ellipsis;
if ($html) {
foreach ($openTags as $tag) {
$truncate .= '</' . $tag . '>';
}
}
return $truncate;
}
/**
* Extracts an excerpt from the text surrounding the phrase with a number of characters on each side
* determined by radius.
*
* @param string $text CakeText to search the phrase in
* @param string $phrase Phrase that will be searched for
* @param int $radius The amount of characters that will be returned on each side of the founded phrase
* @param string $ellipsis Ending that will be appended
* @return string Modified string
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::excerpt
*/
public static function excerpt($text, $phrase, $radius = 100, $ellipsis = '...') {
if (empty($text) || empty($phrase)) {
return static::truncate($text, $radius * 2, array('ellipsis' => $ellipsis));
}
$append = $prepend = $ellipsis;
$phraseLen = mb_strlen($phrase);
$textLen = mb_strlen($text);
$pos = mb_strpos(mb_strtolower($text), mb_strtolower($phrase));
if ($pos === false) {
return mb_substr($text, 0, $radius) . $ellipsis;
}
$startPos = $pos - $radius;
if ($startPos <= 0) {
$startPos = 0;
$prepend = '';
}
$endPos = $pos + $phraseLen + $radius;
if ($endPos >= $textLen) {
$endPos = $textLen;
$append = '';
}
$excerpt = mb_substr($text, $startPos, $endPos - $startPos);
$excerpt = $prepend . $excerpt . $append;
return $excerpt;
}
/**
* Creates a comma separated list where the last two items are joined with 'and', forming natural language.
*
* @param array $list The list to be joined.
* @param string $and The word used to join the last and second last items together with. Defaults to 'and'.
* @param string $separator The separator used to join all the other items together. Defaults to ', '.
* @return string The glued together string.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::toList
*/
public static function toList($list, $and = null, $separator = ', ') {
if ($and === null) {
$and = __d('cake', 'and');
}
if (count($list) > 1) {
return implode($separator, array_slice($list, null, -1)) . ' ' . $and . ' ' . array_pop($list);
}
return array_pop($list);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,17 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Utility
* @since CakePHP(tm) v 0.9.2
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
@ -57,7 +58,7 @@ class ClassRegistry {
*
* @return ClassRegistry instance
*/
public static function &getInstance() {
public static function getInstance() {
static $instance = array();
if (!$instance) {
$instance[0] = new ClassRegistry();
@ -72,30 +73,29 @@ class ClassRegistry {
* Examples
* Simple Use: Get a Post model instance ```ClassRegistry::init('Post');```
*
* Expanded: ```array('class' => 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry', 'type' => 'Model');```
* Expanded: ```array('class' => 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry');```
*
* Model Classes can accept optional ```array('id' => $id, 'table' => $table, 'ds' => $ds, 'alias' => $alias);```
*
* When $class is a numeric keyed array, multiple class instances will be stored in the registry,
* no instance of the object will be returned
* {{{
* ```
* array(
* array('class' => 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry'),
* array('class' => 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry'),
* array('class' => 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry')
* );
* }}}
* ```
*
* @param string|array $class as a string or a single key => value array instance will be created,
* stored in the registry and returned.
* @param boolean $strict if set to true it will return false if the class was not found instead
* @param bool $strict if set to true it will return false if the class was not found instead
* of trying to create an AppModel
* @return object instance of ClassName.
* @return $class instance of ClassName.
* @throws CakeException when you try to construct an interface or abstract class.
*/
public static function init($class, $strict = false) {
$_this = ClassRegistry::getInstance();
$false = false;
$true = true;
if (is_array($class)) {
$objects = $class;
@ -105,19 +105,28 @@ class ClassRegistry {
} else {
$objects = array(array('class' => $class));
}
$defaults = isset($_this->_config['Model']) ? $_this->_config['Model'] : array();
$defaults = array();
if (isset($_this->_config['Model'])) {
$defaults = $_this->_config['Model'];
}
$count = count($objects);
$availableDs = array_keys(ConnectionManager::enumConnectionObjects());
$availableDs = null;
foreach ($objects as $settings) {
if (is_numeric($settings)) {
trigger_error(__d('cake_dev', '(ClassRegistry::init() Attempted to create instance of a class with a numeric name'), E_USER_WARNING);
return false;
}
foreach ($objects as $key => $settings) {
if (is_array($settings)) {
$pluginPath = null;
$settings = array_merge($defaults, $settings);
$settings += $defaults;
$class = $settings['class'];
list($plugin, $class) = pluginSplit($class);
if ($plugin) {
$pluginPath = $plugin . '.';
$settings['plugin'] = $plugin;
}
if (empty($settings['alias'])) {
@ -125,7 +134,8 @@ class ClassRegistry {
}
$alias = $settings['alias'];
if ($model = $_this->_duplicate($alias, $class)) {
$model = $_this->_duplicate($alias, $class);
if ($model) {
$_this->map($alias, $class);
return $model;
}
@ -144,6 +154,9 @@ class ClassRegistry {
$defaultProperties = $reflection->getDefaultProperties();
if (isset($defaultProperties['useDbConfig'])) {
$useDbConfig = $defaultProperties['useDbConfig'];
if ($availableDs === null) {
$availableDs = array_keys(ConnectionManager::enumConnectionObjects());
}
if (in_array('test_' . $useDbConfig, $availableDs)) {
$useDbConfig = 'test_' . $useDbConfig;
}
@ -157,37 +170,27 @@ class ClassRegistry {
} else {
$instance = $reflection->newInstance();
}
if ($strict) {
$instance = ($instance instanceof Model) ? $instance : null;
if ($strict && !$instance instanceof Model) {
$instance = null;
}
}
if (!isset($instance)) {
$appModel = 'AppModel';
if ($strict) {
return false;
} elseif ($plugin && class_exists($plugin . 'AppModel')) {
$appModel = $plugin . 'AppModel';
} else {
$appModel = 'AppModel';
}
if (!empty($appModel)) {
$settings['name'] = $class;
$instance = new $appModel($settings);
}
if (!isset($instance)) {
trigger_error(__d('cake_dev', '(ClassRegistry::init() could not create instance of %s', $class), E_USER_WARNING);
return $false;
}
$settings['name'] = $class;
$instance = new $appModel($settings);
}
$_this->map($alias, $class);
} elseif (is_numeric($settings)) {
trigger_error(__d('cake_dev', '(ClassRegistry::init() Attempted to create instance of a class with a numeric name'), E_USER_WARNING);
return $false;
}
}
if ($count > 1) {
return $true;
return true;
}
return $instance;
}
@ -195,9 +198,9 @@ class ClassRegistry {
/**
* Add $object to the registry, associating it with the name $key.
*
* @param string $key Key for the object in registry
* @param object $object Object to store
* @return boolean True if the object was written, false if $key already exists
* @param string $key Key for the object in registry
* @param object $object Object to store
* @return bool True if the object was written, false if $key already exists
*/
public static function addObject($key, $object) {
$_this = ClassRegistry::getInstance();
@ -212,7 +215,7 @@ class ClassRegistry {
/**
* Remove object which corresponds to given key.
*
* @param string $key Key of object to remove from registry
* @param string $key Key of object to remove from registry
* @return void
*/
public static function removeObject($key) {
@ -227,17 +230,13 @@ class ClassRegistry {
* Returns true if given key is present in the ClassRegistry.
*
* @param string $key Key to look for
* @return boolean true if key exists in registry, false otherwise
* @return bool true if key exists in registry, false otherwise
*/
public static function isKeySet($key) {
$_this = ClassRegistry::getInstance();
$key = Inflector::underscore($key);
if (isset($_this->_objects[$key])) {
return true;
} elseif (isset($_this->_map[$key])) {
return true;
}
return false;
return isset($_this->_objects[$key]) || isset($_this->_map[$key]);
}
/**
@ -246,8 +245,7 @@ class ClassRegistry {
* @return array Set of keys stored in registry
*/
public static function keys() {
$_this = ClassRegistry::getInstance();
return array_keys($_this->_objects);
return array_keys(ClassRegistry::getInstance()->_objects);
}
/**
@ -256,7 +254,7 @@ class ClassRegistry {
* @param string $key Key of object to look for
* @return mixed Object stored in registry or boolean false if the object does not exist.
*/
public static function &getObject($key) {
public static function getObject($key) {
$_this = ClassRegistry::getInstance();
$key = Inflector::underscore($key);
$return = false;
@ -274,10 +272,10 @@ class ClassRegistry {
/**
* Sets the default constructor parameter for an object type
*
* @param string $type Type of object. If this parameter is omitted, defaults to "Model"
* @param string $type Type of object. If this parameter is omitted, defaults to "Model"
* @param array $param The parameter that will be passed to object constructors when objects
* of $type are created
* @return mixed Void if $param is being set. Otherwise, if only $type is passed, returns
* @return mixed Void if $param is being set. Otherwise, if only $type is passed, returns
* the previously-set value of $param, or null if not set.
*/
public static function config($type, $param = array()) {
@ -286,7 +284,7 @@ class ClassRegistry {
if (empty($param) && is_array($type)) {
$param = $type;
$type = 'Model';
} elseif (is_null($param)) {
} elseif ($param === null) {
unset($_this->_config[$type]);
} elseif (empty($param) && is_string($type)) {
return isset($_this->_config[$type]) ? $_this->_config[$type] : null;
@ -300,15 +298,15 @@ class ClassRegistry {
/**
* Checks to see if $alias is a duplicate $class Object
*
* @param string $alias
* @param string $class
* @return boolean
* @param string $alias Alias to check.
* @param string $class Class name.
* @return bool
*/
protected function &_duplicate($alias, $class) {
protected function &_duplicate($alias, $class) {
$duplicate = false;
if ($this->isKeySet($alias)) {
$model = $this->getObject($alias);
if (is_object($model) && (is_a($model, $class) || $model->alias === $class)) {
if (is_object($model) && ($model instanceof $class || $model->alias === $class)) {
$duplicate = $model;
}
unset($model);
@ -338,8 +336,7 @@ class ClassRegistry {
* @return array Keys of registry's map
*/
public static function mapKeys() {
$_this = ClassRegistry::getInstance();
return array_keys($_this->_map);
return array_keys(ClassRegistry::getInstance()->_map);
}
/**

View file

@ -4,23 +4,22 @@
*
* Provides enhanced logging, stack traces, and rendering debug views
*
* PHP 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Utility
* @since CakePHP(tm) v 1.2.4560
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
App::uses('CakeLog', 'Log');
App::uses('String', 'Utility');
App::uses('CakeText', 'Utility');
/**
* Provide custom logging and error handling.
@ -47,7 +46,7 @@ class Debugger {
protected $_outputFormat = 'js';
/**
* Templates used when generating trace or error strings. Can be global or indexed by the format
* Templates used when generating trace or error strings. Can be global or indexed by the format
* value used in $_outputFormat.
*
* @var string
@ -80,8 +79,7 @@ class Debugger {
'traceLine' => '{:reference} - {:path}, line {:line}',
'trace' => "Trace:\n{:trace}\n",
'context' => "Context:\n{:context}\n",
),
'log' => array(),
)
);
/**
@ -93,7 +91,6 @@ class Debugger {
/**
* Constructor.
*
*/
public function __construct() {
$docRef = ini_get('docref_root');
@ -150,10 +147,10 @@ class Debugger {
/**
* Returns a reference to the Debugger singleton object instance.
*
* @param string $class
* @param string $class Debugger class name.
* @return object
*/
public static function &getInstance($class = null) {
public static function getInstance($class = null) {
static $instance = array();
if (!empty($class)) {
if (!$instance || strtolower($class) != strtolower(get_class($instance[0]))) {
@ -169,40 +166,41 @@ class Debugger {
/**
* Recursively formats and outputs the contents of the supplied variable.
*
*
* @param mixed $var the variable to dump
* @param int $depth The depth to output to. Defaults to 3.
* @return void
* @see Debugger::exportVar()
* @link http://book.cakephp.org/2.0/en/development/debugging.html#Debugger::dump
*/
public static function dump($var) {
pr(self::exportVar($var));
public static function dump($var, $depth = 3) {
pr(static::exportVar($var, $depth));
}
/**
* Creates an entry in the log file. The log entry will contain a stack trace from where it was called.
* Creates an entry in the log file. The log entry will contain a stack trace from where it was called.
* as well as export the variable using exportVar. By default the log is written to the debug log.
*
* @param mixed $var Variable or content to log
* @param integer $level type of log to use. Defaults to LOG_DEBUG
* @param int $level type of log to use. Defaults to LOG_DEBUG
* @param int $depth The depth to output to. Defaults to 3.
* @return void
* @link http://book.cakephp.org/2.0/en/development/debugging.html#Debugger::log
*/
public static function log($var, $level = LOG_DEBUG) {
$source = self::trace(array('start' => 1)) . "\n";
CakeLog::write($level, "\n" . $source . self::exportVar($var));
public static function log($var, $level = LOG_DEBUG, $depth = 3) {
$source = static::trace(array('start' => 1)) . "\n";
CakeLog::write($level, "\n" . $source . static::exportVar($var, $depth));
}
/**
* Overrides PHP's default error handling.
*
* @param integer $code Code of error
* @param int $code Code of error
* @param string $description Error description
* @param string $file File on which error occurred
* @param integer $line Line that triggered the error
* @param int $line Line that triggered the error
* @param array $context Context
* @return boolean true if error was handled
* @deprecated This function is superseded by Debugger::outputError()
* @return bool|null True if error was handled, otherwise null.
* @deprecated 3.0.0 Will be removed in 3.0. This function is superseded by Debugger::outputError().
*/
public static function showError($code, $description, $file = null, $line = null, $context = null) {
$self = Debugger::getInstance();
@ -218,7 +216,7 @@ class Debugger {
if (!in_array($info, $self->errors)) {
$self->errors[] = $info;
} else {
return;
return null;
}
switch ($code) {
@ -229,26 +227,26 @@ class Debugger {
case E_USER_ERROR:
$error = 'Fatal Error';
$level = LOG_ERR;
break;
break;
case E_WARNING:
case E_USER_WARNING:
case E_COMPILE_WARNING:
case E_RECOVERABLE_ERROR:
$error = 'Warning';
$level = LOG_WARNING;
break;
break;
case E_NOTICE:
case E_USER_NOTICE:
$error = 'Notice';
$level = LOG_NOTICE;
break;
break;
case E_DEPRECATED:
case E_USER_DEPRECATED:
$error = 'Deprecated';
$level = LOG_NOTICE;
break;
break;
default:
return;
return null;
}
$data = compact(
@ -256,7 +254,7 @@ class Debugger {
);
echo $self->outputError($data);
if ($error == 'Fatal Error') {
if ($error === 'Fatal Error') {
exit();
}
return true;
@ -268,11 +266,11 @@ class Debugger {
* ### Options
*
* - `depth` - The number of stack frames to return. Defaults to 999
* - `format` - The format you want the return. Defaults to the currently selected format. If
* - `format` - The format you want the return. Defaults to the currently selected format. If
* format is 'array' or 'points' the return will be an array.
* - `args` - Should arguments for functions be shown? If true, the arguments for each method call
* will be displayed.
* - `start` - The stack frame to start generating a trace from. Defaults to 0
* - `start` - The stack frame to start generating a trace from. Defaults to 0
*
* @param array $options Format for outputting stack trace
* @return mixed Formatted stack trace
@ -295,9 +293,9 @@ class Debugger {
$back = array();
$_trace = array(
'line' => '??',
'file' => '[internal]',
'class' => null,
'line' => '??',
'file' => '[internal]',
'class' => null,
'function' => '[main]'
);
@ -317,7 +315,7 @@ class Debugger {
foreach ($next['args'] as $arg) {
$args[] = Debugger::exportVar($arg);
}
$reference .= join(', ', $args);
$reference .= implode(', ', $args);
}
$reference .= ')';
}
@ -325,9 +323,9 @@ class Debugger {
if (in_array($signature, $options['exclude'])) {
continue;
}
if ($options['format'] == 'points' && $trace['file'] != '[internal]') {
if ($options['format'] === 'points' && $trace['file'] !== '[internal]') {
$back[] = array('file' => $trace['file'], 'line' => $trace['line']);
} elseif ($options['format'] == 'array') {
} elseif ($options['format'] === 'array') {
$back[] = $trace;
} else {
if (isset($self->_templates[$options['format']]['traceLine'])) {
@ -335,14 +333,14 @@ class Debugger {
} else {
$tpl = $self->_templates['base']['traceLine'];
}
$trace['path'] = self::trimPath($trace['file']);
$trace['path'] = static::trimPath($trace['file']);
$trace['reference'] = $reference;
unset($trace['object'], $trace['args']);
$back[] = String::insert($tpl, $trace, array('before' => '{:', 'after' => '}'));
$back[] = CakeText::insert($tpl, $trace, array('before' => '{:', 'after' => '}'));
}
}
if ($options['format'] == 'array' || $options['format'] == 'points') {
if ($options['format'] === 'array' || $options['format'] === 'points') {
return $back;
}
return implode("\n", $back);
@ -379,13 +377,13 @@ class Debugger {
* `Debugger::excerpt('/path/to/file', 100, 4);`
*
* The above would return an array of 8 items. The 4th item would be the provided line,
* and would be wrapped in `<span class="code-highlight"></span>`. All of the lines
* and would be wrapped in `<span class="code-highlight"></span>`. All of the lines
* are processed with highlight_string() as well, so they have basic PHP syntax highlighting
* applied.
*
* @param string $file Absolute path to a PHP file
* @param integer $line Line number to highlight
* @param integer $context Number of lines of context to extract above and below $line
* @param int $line Line number to highlight
* @param int $context Number of lines of context to extract above and below $line
* @return array Set of lines highlighted
* @see http://php.net/highlight_string
* @link http://book.cakephp.org/2.0/en/development/debugging.html#Debugger::excerpt
@ -409,7 +407,7 @@ class Debugger {
if (!isset($data[$i])) {
continue;
}
$string = str_replace(array("\r\n", "\n"), "", self::_highlight($data[$i]));
$string = str_replace(array("\r\n", "\n"), "", static::_highlight($data[$i]));
if ($i == $line) {
$lines[] = '<span class="code-highlight">' . $string . '</span>';
} else {
@ -420,7 +418,7 @@ class Debugger {
}
/**
* Wraps the highlight_string funciton in case the server API does not
* Wraps the highlight_string function in case the server API does not
* implement the function as it is the case of the HipHop interpreter
*
* @param string $str the string to convert
@ -457,31 +455,29 @@ class Debugger {
* - host
* - database
* - port
* - prefix
* - schema
*
* This is done to protect database credentials, which could be accidentally
* shown in an error message if CakePHP is deployed in development mode.
*
* @param string $var Variable to convert
* @param integer $depth The depth to output to. Defaults to 3.
* @param int $depth The depth to output to. Defaults to 3.
* @return string Variable as a formatted string
* @link http://book.cakephp.org/2.0/en/development/debugging.html#Debugger::exportVar
*/
public static function exportVar($var, $depth = 3) {
return self::_export($var, $depth, 0);
return static::_export($var, $depth, 0);
}
/**
* Protected export function used to keep track of indentation and recursion.
*
* @param mixed $var The variable to dump.
* @param integer $depth The remaining depth.
* @param integer $indent The current indentation level.
* @param int $depth The remaining depth.
* @param int $indent The current indentation level.
* @return string The dumped variable.
*/
protected static function _export($var, $depth, $indent) {
switch (self::getType($var)) {
switch (static::getType($var)) {
case 'boolean':
return ($var) ? 'true' : 'false';
case 'integer':
@ -489,23 +485,25 @@ class Debugger {
case 'float':
return '(float) ' . $var;
case 'string':
if (trim($var) == '') {
if (trim($var) === '') {
return "''";
}
return "'" . $var . "'";
case 'array':
return self::_array($var, $depth - 1, $indent + 1);
return static::_array($var, $depth - 1, $indent + 1);
case 'resource':
return strtolower(gettype($var));
case 'null':
return 'null';
case 'unknown':
return 'unknown';
default:
return self::_object($var, $depth - 1, $indent + 1);
return static::_object($var, $depth - 1, $indent + 1);
}
}
/**
* Export an array type object. Filters out keys used in datasource configuration.
* Export an array type object. Filters out keys used in datasource configuration.
*
* The following keys are replaced with ***'s
*
@ -514,31 +512,26 @@ class Debugger {
* - host
* - database
* - port
* - prefix
* - schema
*
* @param array $var The array to export.
* @param integer $depth The current depth, used for recursion tracking.
* @param integer $indent The current indentation level.
* @param int $depth The current depth, used for recursion tracking.
* @param int $indent The current indentation level.
* @return string Exported array.
*/
protected static function _array(array $var, $depth, $indent) {
$secrets = array(
'password' => '*****',
'login' => '*****',
'login' => '*****',
'host' => '*****',
'database' => '*****',
'port' => '*****',
'prefix' => '*****',
'schema' => '*****'
'port' => '*****'
);
$replace = array_intersect_key($secrets, $var);
$var = $replace + $var;
$out = "array(";
$n = $break = $end = null;
$break = $end = null;
if (!empty($var)) {
$n = "\n";
$break = "\n" . str_repeat("\t", $indent);
$end = "\n" . str_repeat("\t", $indent - 1);
}
@ -549,10 +542,10 @@ class Debugger {
// Sniff for globals as !== explodes in < 5.4
if ($key === 'GLOBALS' && is_array($val) && isset($val['GLOBALS'])) {
$val = '[recursion]';
} else if ($val !== $var) {
$val = self::_export($val, $depth, $indent);
} elseif ($val !== $var) {
$val = static::_export($val, $depth, $indent);
}
$vars[] = $break . self::exportVar($key) .
$vars[] = $break . static::exportVar($key) .
' => ' .
$val;
}
@ -566,8 +559,8 @@ class Debugger {
* Handles object to string conversion.
*
* @param string $var Object to convert
* @param integer $depth The current depth, used for tracking recursion.
* @param integer $indent The current indentation level.
* @param int $depth The current depth, used for tracking recursion.
* @param int $indent The current indentation level.
* @return string
* @see Debugger::exportVar()
*/
@ -583,9 +576,30 @@ class Debugger {
$break = "\n" . str_repeat("\t", $indent);
$objectVars = get_object_vars($var);
foreach ($objectVars as $key => $value) {
$value = self::_export($value, $depth - 1, $indent);
$value = static::_export($value, $depth - 1, $indent);
$props[] = "$key => " . $value;
}
if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
$ref = new ReflectionObject($var);
$filters = array(
ReflectionProperty::IS_PROTECTED => 'protected',
ReflectionProperty::IS_PRIVATE => 'private',
);
foreach ($filters as $filter => $visibility) {
$reflectionProperties = $ref->getProperties($filter);
foreach ($reflectionProperties as $reflectionProperty) {
$reflectionProperty->setAccessible(true);
$property = $reflectionProperty->getValue($var);
$value = static::_export($property, $depth - 1, $indent);
$key = $reflectionProperty->name;
$props[] = sprintf('[%s] %s => %s', $visibility, $key, $value);
}
}
}
$out .= $break . implode($break, $props) . $end;
}
$out .= '}';
@ -597,7 +611,7 @@ class Debugger {
*
* @param string $format The format you want errors to be output as.
* Leave null to get the current format.
* @return mixed Returns null when setting. Returns the current format when getting.
* @return mixed Returns null when setting. Returns the current format when getting.
* @throws CakeException when choosing a format that doesn't exist.
*/
public static function outputAs($format = null) {
@ -616,8 +630,8 @@ class Debugger {
*
* `Debugger::addFormat('custom', $data);`
*
* Where $data is an array of strings that use String::insert() variable
* replacement. The template vars should be in a `{:id}` style.
* Where $data is an array of strings that use CakeText::insert() variable
* replacement. The template vars should be in a `{:id}` style.
* An error formatter can have the following keys:
*
* - 'error' - Used for the container for the error message. Gets the following template
@ -630,7 +644,7 @@ class Debugger {
* Gets the following templates: `id`, `context`
* - 'links' - An array of HTML links that are used for creating links to other resources.
* Typically this is used to create javascript links to open other sections.
* Link keys, are: `code`, `context`, `help`. See the js output format for an
* Link keys, are: `code`, `context`, `help`. See the js output format for an
* example.
* - 'traceLine' - Used for creating lines in the stacktrace. Gets the following
* template variables: `reference`, `path`, `line`
@ -640,9 +654,9 @@ class Debugger {
*
* `Debugger::addFormat('custom', array('callback' => array($foo, 'outputError'));`
*
* The callback can expect two parameters. The first is an array of all
* The callback can expect two parameters. The first is an array of all
* the error data. The second contains the formatted strings generated using
* the other template strings. Keys like `info`, `links`, `code`, `context` and `trace`
* the other template strings. Keys like `info`, `links`, `code`, `context` and `trace`
* will be present depending on the other templates in the format type.
*
* @param string $format Format to use, including 'js' for JavaScript-enhanced HTML, 'html' for
@ -675,14 +689,14 @@ class Debugger {
* straight HTML output, or 'txt' for unformatted text.
* @param array $strings Template strings to be used for the output format.
* @return string
* @deprecated Use Debugger::outputAs() and Debugger::addFormat(). Will be removed
* @deprecated 3.0.0 Use Debugger::outputAs() and Debugger::addFormat(). Will be removed
* in 3.0
*/
public function output($format = null, $strings = array()) {
public static function output($format = null, $strings = array()) {
$self = Debugger::getInstance();
$data = null;
if (is_null($format)) {
if ($format === null) {
return Debugger::outputAs();
}
@ -702,7 +716,7 @@ class Debugger {
/**
* Takes a processed array of data from an error and displays it in the chosen format.
*
* @param string $data
* @param string $data Data to output.
* @return void
*/
public function outputError($data) {
@ -754,12 +768,13 @@ class Debugger {
if (isset($tpl['links'])) {
foreach ($tpl['links'] as $key => $val) {
$links[$key] = String::insert($val, $data, $insertOpts);
$links[$key] = CakeText::insert($val, $data, $insertOpts);
}
}
if (!empty($tpl['escapeContext'])) {
$context = h($context);
$data['description'] = h($data['description']);
}
$infoData = compact('code', 'context', 'trace');
@ -768,20 +783,20 @@ class Debugger {
continue;
}
if (is_array($value)) {
$value = join("\n", $value);
$value = implode("\n", $value);
}
$info .= String::insert($tpl[$key], array($key => $value) + $data, $insertOpts);
$info .= CakeText::insert($tpl[$key], array($key => $value) + $data, $insertOpts);
}
$links = join(' ', $links);
$links = implode(' ', $links);
if (isset($tpl['callback']) && is_callable($tpl['callback'])) {
return call_user_func($tpl['callback'], $data, compact('links', 'info'));
}
echo String::insert($tpl['error'], compact('links', 'info') + $data, $insertOpts);
echo CakeText::insert($tpl['error'], compact('links', 'info') + $data, $insertOpts);
}
/**
* Get the type of the given variable. Will return the classname
* Get the type of the given variable. Will return the class name
* for objects.
*
* @param mixed $var The variable to get the type of
@ -791,7 +806,7 @@ class Debugger {
if (is_object($var)) {
return get_class($var);
}
if (is_null($var)) {
if ($var === null) {
return 'null';
}
if (is_string($var)) {
@ -821,12 +836,12 @@ class Debugger {
* @return void
*/
public static function checkSecurityKeys() {
if (Configure::read('Security.salt') == 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi') {
trigger_error(__d('cake_dev', 'Please change the value of \'Security.salt\' in app/Config/core.php to a salt value specific to your application'), E_USER_NOTICE);
if (Configure::read('Security.salt') === 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi') {
trigger_error(__d('cake_dev', 'Please change the value of %s in %s to a salt value specific to your application.', '\'Security.salt\'', 'APP/Config/core.php'), E_USER_NOTICE);
}
if (Configure::read('Security.cipherSeed') === '76859309657453542496749683645') {
trigger_error(__d('cake_dev', 'Please change the value of \'Security.cipherSeed\' in app/Config/core.php to a numeric (digits only) seed value specific to your application'), E_USER_NOTICE);
trigger_error(__d('cake_dev', 'Please change the value of %s in %s to a numeric (digits only) seed value specific to your application.', '\'Security.cipherSeed\'', 'APP/Config/core.php'), E_USER_NOTICE);
}
}

View file

@ -2,19 +2,18 @@
/**
* Convenience class for reading, writing and appending to files.
*
* PHP 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Utility
* @since CakePHP(tm) v 0.2.9
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
App::uses('Folder', 'Utility');
@ -27,7 +26,7 @@ App::uses('Folder', 'Utility');
class File {
/**
* Folder object of the File
* Folder object of the file
*
* @var Folder
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::$Folder
@ -35,7 +34,7 @@ class File {
public $Folder = null;
/**
* Filename
* File name
*
* @var string
* http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::$name
@ -61,7 +60,7 @@ class File {
/**
* Enable locking for file reading and writing
*
* @var boolean
* @var bool
* http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::$lock
*/
public $lock = null;
@ -71,7 +70,7 @@ class File {
*
* Current file's absolute path
*
* @var mixed null
* @var mixed
* http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::$path
*/
public $path = null;
@ -80,8 +79,8 @@ class File {
* Constructor
*
* @param string $path Path to file
* @param boolean $create Create file if it does not exist (if true)
* @param integer $mode Mode to apply to the folder holding the file
* @param bool $create Create file if it does not exist (if true)
* @param int $mode Mode to apply to the folder holding the file
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File
*/
public function __construct($path, $create = false, $mode = 0755) {
@ -95,16 +94,15 @@ class File {
/**
* Closes the current file if it is opened
*
*/
public function __destruct() {
$this->close();
}
/**
* Creates the File.
* Creates the file.
*
* @return boolean Success
* @return bool Success
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::create
*/
public function create() {
@ -121,15 +119,14 @@ class File {
* Opens the current file with a given $mode
*
* @param string $mode A valid 'fopen' mode string (r|w|a ...)
* @param boolean $force If true then the file will be re-opened even if its already opened, otherwise it won't
* @return boolean True on success, false on failure
* @param bool $force If true then the file will be re-opened even if its already opened, otherwise it won't
* @return bool True on success, false on failure
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::open
*/
public function open($mode = 'r', $force = false) {
if (!$force && is_resource($this->handle)) {
return true;
}
clearstatcache();
if ($this->exists() === false) {
if ($this->create() === false) {
return false;
@ -144,11 +141,11 @@ class File {
}
/**
* Return the contents of this File as a string.
* Return the contents of this file as a string.
*
* @param string $bytes where to start
* @param string $mode A `fread` compatible mode.
* @param boolean $force If true then the file will be re-opened even if its already opened, otherwise it won't
* @param bool $force If true then the file will be re-opened even if its already opened, otherwise it won't
* @return mixed string on success, false on failure
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::read
*/
@ -183,8 +180,8 @@ class File {
/**
* Sets or gets the offset for the currently opened file.
*
* @param integer|boolean $offset The $offset in bytes to seek. If set to false then the current offset is returned.
* @param integer $seek PHP Constant SEEK_SET | SEEK_CUR | SEEK_END determining what the $offset is relative to
* @param int|bool $offset The $offset in bytes to seek. If set to false then the current offset is returned.
* @param int $seek PHP Constant SEEK_SET | SEEK_CUR | SEEK_END determining what the $offset is relative to
* @return mixed True on success, false on failure (set mode), false on failure or integer offset on success (get mode)
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::offset
*/
@ -200,30 +197,30 @@ class File {
}
/**
* Prepares a ascii string for writing. Converts line endings to the
* correct terminator for the current platform. If windows "\r\n" will be used
* Prepares an ASCII string for writing. Converts line endings to the
* correct terminator for the current platform. If Windows, "\r\n" will be used,
* all other platforms will use "\n"
*
* @param string $data Data to prepare for writing.
* @param boolean $forceWindows
* @param bool $forceWindows If true forces usage Windows newline string.
* @return string The with converted line endings.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::prepare
*/
public static function prepare($data, $forceWindows = false) {
$lineBreak = "\n";
if (DIRECTORY_SEPARATOR == '\\' || $forceWindows === true) {
if (DIRECTORY_SEPARATOR === '\\' || $forceWindows === true) {
$lineBreak = "\r\n";
}
return strtr($data, array("\r\n" => $lineBreak, "\n" => $lineBreak, "\r" => $lineBreak));
}
/**
* Write given data to this File.
* Write given data to this file.
*
* @param string $data Data to write to this File.
* @param string $mode Mode of writing. {@link http://php.net/fwrite See fwrite()}.
* @param string $force force the file to open
* @return boolean Success
* @param bool $force Force the file to open
* @return bool Success
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::write
*/
public function write($data, $mode = 'w', $force = false) {
@ -246,11 +243,11 @@ class File {
}
/**
* Append given data string to this File.
* Append given data string to this file.
*
* @param string $data Data to write
* @param string $force force the file to open
* @return boolean Success
* @param string $force Force the file to open
* @return bool Success
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::append
*/
public function append($data, $force = false) {
@ -260,7 +257,7 @@ class File {
/**
* Closes the current file if it is opened.
*
* @return boolean True if closing was successful or file was already closed, otherwise false
* @return bool True if closing was successful or file was already closed, otherwise false
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::close
*/
public function close() {
@ -271,13 +268,12 @@ class File {
}
/**
* Deletes the File.
* Deletes the file.
*
* @return boolean Success
* @return bool Success
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::delete
*/
public function delete() {
clearstatcache();
if (is_resource($this->handle)) {
fclose($this->handle);
$this->handle = null;
@ -289,7 +285,7 @@ class File {
}
/**
* Returns the File info as an array with the following keys:
* Returns the file info as an array with the following keys:
*
* - dirname
* - basename
@ -302,7 +298,7 @@ class File {
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::info
*/
public function info() {
if ($this->info == null) {
if (!$this->info) {
$this->info = pathinfo($this->path);
}
if (!isset($this->info['filename'])) {
@ -318,13 +314,13 @@ class File {
}
/**
* Returns the File extension.
* Returns the file extension.
*
* @return string The File extension
* @return string The file extension
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::ext
*/
public function ext() {
if ($this->info == null) {
if (!$this->info) {
$this->info();
}
if (isset($this->info['extension'])) {
@ -334,13 +330,13 @@ class File {
}
/**
* Returns the File name without extension.
* Returns the file name without extension.
*
* @return string The File name without extension.
* @return string The file name without extension.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::name
*/
public function name() {
if ($this->info == null) {
if (!$this->info) {
$this->info();
}
if (isset($this->info['extension'])) {
@ -352,11 +348,11 @@ class File {
}
/**
* makes filename safe for saving
* Makes file name safe for saving
*
* @param string $name The name of the file to make safe if different from $this->name
* @param string $ext The name of the extension to make safe if different from $this->ext
* @return string $ext the extension of the file
* @return string ext The extension of the file
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::safe
*/
public function safe($name = null, $ext = null) {
@ -372,8 +368,8 @@ class File {
/**
* Get md5 Checksum of file with previous check of Filesize
*
* @param integer|boolean $maxsize in MB or true to force
* @return string md5 Checksum {@link http://php.net/md5_file See md5_file()}
* @param int|bool $maxsize in MB or true to force
* @return string|false md5 Checksum {@link http://php.net/md5_file See md5_file()}, or false in case of an error
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::md5
*/
public function md5($maxsize = 5) {
@ -390,32 +386,36 @@ class File {
}
/**
* Returns the full path of the File.
* Returns the full path of the file.
*
* @return string Full path to file
* @return string Full path to the file
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::pwd
*/
public function pwd() {
if (is_null($this->path)) {
$this->path = $this->Folder->slashTerm($this->Folder->pwd()) . $this->name;
if ($this->path === null) {
$dir = $this->Folder->pwd();
if (is_dir($dir)) {
$this->path = $this->Folder->slashTerm($dir) . $this->name;
}
}
return $this->path;
}
/**
* Returns true if the File exists.
* Returns true if the file exists.
*
* @return boolean true if it exists, false otherwise
* @return bool True if it exists, false otherwise
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::exists
*/
public function exists() {
$this->clearStatCache();
return (file_exists($this->path) && is_file($this->path));
}
/**
* Returns the "chmod" (permissions) of the File.
* Returns the "chmod" (permissions) of the file.
*
* @return string Permissions for the file
* @return string|false Permissions for the file, or false in case of an error
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::perms
*/
public function perms() {
@ -426,9 +426,9 @@ class File {
}
/**
* Returns the Filesize
* Returns the file size
*
* @return integer size of the file in bytes, or false in case of an error
* @return int|false Size of the file in bytes, or false in case of an error
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::size
*/
public function size() {
@ -439,9 +439,9 @@ class File {
}
/**
* Returns true if the File is writable.
* Returns true if the file is writable.
*
* @return boolean true if its writable, false otherwise
* @return bool True if it's writable, false otherwise
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::writable
*/
public function writable() {
@ -451,7 +451,7 @@ class File {
/**
* Returns true if the File is executable.
*
* @return boolean true if its executable, false otherwise
* @return bool True if it's executable, false otherwise
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::executable
*/
public function executable() {
@ -459,9 +459,9 @@ class File {
}
/**
* Returns true if the File is readable.
* Returns true if the file is readable.
*
* @return boolean true if file is readable, false otherwise
* @return bool True if file is readable, false otherwise
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::readable
*/
public function readable() {
@ -469,9 +469,9 @@ class File {
}
/**
* Returns the File's owner.
* Returns the file's owner.
*
* @return integer the Fileowner
* @return int|false The file owner, or false in case of an error
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::owner
*/
public function owner() {
@ -482,9 +482,9 @@ class File {
}
/**
* Returns the File's group.
* Returns the file's group.
*
* @return integer the Filegroup
* @return int|false The file group, or false in case of an error
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::group
*/
public function group() {
@ -497,7 +497,7 @@ class File {
/**
* Returns last access time.
*
* @return integer timestamp Timestamp of last access time
* @return int|false Timestamp of last access time, or false in case of an error
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::lastAccess
*/
public function lastAccess() {
@ -510,7 +510,7 @@ class File {
/**
* Returns last modified time.
*
* @return integer timestamp Timestamp of last modification
* @return int|false Timestamp of last modification, or false in case of an error
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::lastChange
*/
public function lastChange() {
@ -526,16 +526,16 @@ class File {
* @return Folder Current folder
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::Folder
*/
public function &folder() {
public function folder() {
return $this->Folder;
}
/**
* Copy the File to $dest
*
* @param string $dest destination for the copy
* @param boolean $overwrite Overwrite $dest if exists
* @return boolean Success
* @param string $dest Destination for the copy
* @param bool $overwrite Overwrite $dest if exists
* @return bool Success
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::copy
*/
public function copy($dest, $overwrite = true) {
@ -546,7 +546,7 @@ class File {
}
/**
* Get the mime type of the file. Uses the finfo extension if
* Get the mime type of the file. Uses the finfo extension if
* its available, otherwise falls back to mime_content_type
*
* @return false|string The mimetype of the file, or false if reading fails.
@ -557,12 +557,62 @@ class File {
}
if (function_exists('finfo_open')) {
$finfo = finfo_open(FILEINFO_MIME);
list($type, $charset) = explode(';', finfo_file($finfo, $this->pwd()));
$finfo = finfo_file($finfo, $this->pwd());
if (!$finfo) {
return false;
}
list($type) = explode(';', $finfo);
return $type;
} elseif (function_exists('mime_content_type')) {
}
if (function_exists('mime_content_type')) {
return mime_content_type($this->pwd());
}
return false;
}
/**
* Clear PHP's internal stat cache
*
* For 5.3 onwards it's possible to clear cache for just a single file. Passing true
* will clear all the stat cache.
*
* @param bool $all Clear all cache or not
* @return void
*/
public function clearStatCache($all = false) {
if ($all === false && version_compare(PHP_VERSION, '5.3.0') >= 0) {
return clearstatcache(true, $this->path);
}
return clearstatcache();
}
/**
* Searches for a given text and replaces the text if found.
*
* @param string|array $search Text(s) to search for.
* @param string|array $replace Text(s) to replace with.
* @return bool Success
*/
public function replaceText($search, $replace) {
if (!$this->open('r+')) {
return false;
}
if ($this->lock !== null) {
if (flock($this->handle, LOCK_EX) === false) {
return false;
}
}
$replaced = $this->write(str_replace($search, $replace, $this->read()), 'w', true);
if ($this->lock !== null) {
flock($this->handle, LOCK_UN);
}
$this->close();
return $replaced;
}
}

View file

@ -1,16 +1,17 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Utility
* @since CakePHP(tm) v 0.2.9
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
@ -21,6 +22,40 @@
*/
class Folder {
/**
* Default scheme for Folder::copy
* Recursively merges subfolders with the same name
*
* @var string
*/
const MERGE = 'merge';
/**
* Overwrite scheme for Folder::copy
* subfolders with the same name will be replaced
*
* @var string
*/
const OVERWRITE = 'overwrite';
/**
* Skip scheme for Folder::copy
* if a subfolder with the same name exists it will be skipped
*
* @var string
*/
const SKIP = 'skip';
/**
* Sort mode by name
*/
const SORT_NAME = 'name';
/**
* Sort mode by time
*/
const SORT_TIME = 'time';
/**
* Path to Folder.
*
@ -33,19 +68,27 @@ class Folder {
* Sortedness. Whether or not list results
* should be sorted by name.
*
* @var boolean
* @var bool
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::$sort
*/
public $sort = false;
/**
* Mode to be used on create. Does nothing on windows platforms.
* Mode to be used on create. Does nothing on Windows platforms.
*
* @var integer
* @var int
* http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::$mode
*/
public $mode = 0755;
/**
* Functions array to be called depending on the sort type chosen.
*/
protected $_fsorts = array(
self::SORT_NAME => 'getPathname',
self::SORT_TIME => 'getCTime'
);
/**
* Holds messages from last method.
*
@ -78,8 +121,8 @@ class Folder {
* Constructor.
*
* @param string $path Path to folder
* @param boolean $create Create folder if not found
* @param string|boolean $mode Mode (CHMOD) to apply to created folder, false to ignore
* @param bool $create Create folder if not found
* @param string|bool $mode Mode (CHMOD) to apply to created folder, false to ignore
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder
*/
public function __construct($path = false, $create = false, $mode = false) {
@ -130,14 +173,14 @@ class Folder {
* Returns an array of the contents of the current directory.
* The returned array holds two arrays: One of directories and one of files.
*
* @param boolean $sort Whether you want the results sorted, set this and the sort property
* @param string|bool $sort Whether you want the results sorted, set this and the sort property
* to false to get unsorted results.
* @param array|boolean $exceptions Either an array or boolean true will not grab dot files
* @param boolean $fullPath True returns the full path
* @param array|bool $exceptions Either an array or boolean true will not grab dot files
* @param bool $fullPath True returns the full path
* @return mixed Contents of current directory as an array, an empty array on failure
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::read
*/
public function read($sort = true, $exceptions = false, $fullPath = false) {
public function read($sort = self::SORT_NAME, $exceptions = false, $fullPath = false) {
$dirs = $files = array();
if (!$this->pwd()) {
@ -153,6 +196,11 @@ class Folder {
} catch (Exception $e) {
return array($dirs, $files);
}
if (!is_bool($sort) && isset($this->_fsorts[$sort])) {
$methodName = $this->_fsorts[$sort];
} else {
$methodName = $this->_fsorts[self::SORT_NAME];
}
foreach ($iterator as $item) {
if ($item->isDot()) {
@ -166,14 +214,22 @@ class Folder {
$name = $item->getPathName();
}
if ($item->isDir()) {
$dirs[] = $name;
$dirs[$item->{$methodName}()][] = $name;
} else {
$files[] = $name;
$files[$item->{$methodName}()][] = $name;
}
}
if ($sort || $this->sort) {
sort($dirs);
sort($files);
ksort($dirs);
ksort($files);
}
if ($dirs) {
$dirs = call_user_func_array('array_merge', $dirs);
}
if ($files) {
$files = call_user_func_array('array_merge', $files);
}
return array($dirs, $files);
}
@ -182,12 +238,12 @@ class Folder {
* Returns an array of all matching files in current directory.
*
* @param string $regexpPattern Preg_match pattern (Defaults to: .*)
* @param boolean $sort Whether results should be sorted.
* @param bool $sort Whether results should be sorted.
* @return array Files that match given pattern
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::find
*/
public function find($regexpPattern = '.*', $sort = false) {
list($dirs, $files) = $this->read($sort);
list(, $files) = $this->read($sort);
return array_values(preg_grep('/^' . $regexpPattern . '$/i', $files));
}
@ -195,7 +251,7 @@ class Folder {
* Returns an array of all matching files in and below current directory.
*
* @param string $pattern Preg_match pattern (Defaults to: .*)
* @param boolean $sort Whether results should be sorted.
* @param bool $sort Whether results should be sorted.
* @return array Files matching $pattern
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::findRecursive
*/
@ -213,7 +269,7 @@ class Folder {
* Private helper function for findRecursive.
*
* @param string $pattern Pattern to match against
* @param boolean $sort Whether results should be sorted.
* @param bool $sort Whether results should be sorted.
* @return array Files matching pattern
*/
protected function _findRecursive($pattern, $sort = false) {
@ -238,22 +294,44 @@ class Folder {
* Returns true if given $path is a Windows path.
*
* @param string $path Path to check
* @return boolean true if windows path, false otherwise
* @return bool true if Windows path, false otherwise
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::isWindowsPath
*/
public static function isWindowsPath($path) {
return (preg_match('/^[A-Z]:\\\\/i', $path) || substr($path, 0, 2) == '\\\\');
return (preg_match('/^[A-Z]:\\\\/i', $path) || substr($path, 0, 2) === '\\\\');
}
/**
* Returns true if given $path is an absolute path.
*
* @param string $path Path to check
* @return boolean true if path is absolute.
* @return bool true if path is absolute.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::isAbsolute
*/
public static function isAbsolute($path) {
return !empty($path) && ($path[0] === '/' || preg_match('/^[A-Z]:\\\\/i', $path) || substr($path, 0, 2) == '\\\\');
if (empty($path)) {
return false;
}
return $path[0] === '/' ||
preg_match('/^[A-Z]:\\\\/i', $path) ||
substr($path, 0, 2) === '\\\\' ||
static::isRegisteredStreamWrapper($path);
}
/**
* Returns true if given $path is a registered stream wrapper.
*
* @param string $path Path to check
* @return boo true If path is registered stream wrapper.
*/
public static function isRegisteredStreamWrapper($path) {
if (preg_match('/^[A-Z]+(?=:\/\/)/i', $path, $matches) &&
in_array($matches[0], stream_get_wrappers())
) {
return true;
}
return false;
}
/**
@ -296,19 +374,21 @@ class Folder {
* Returns $path with $element added, with correct slash in-between.
*
* @param string $path Path
* @param string $element Element to and at end of path
* @param string|array $element Element to add at end of path
* @return string Combined path
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::addPathElement
*/
public static function addPathElement($path, $element) {
return rtrim($path, DS) . DS . $element;
$element = (array)$element;
array_unshift($element, rtrim($path, DS));
return implode(DS, $element);
}
/**
* Returns true if the File is in a given CakePath.
* Returns true if the Folder is in the given Cake path.
*
* @param string $path The path to check.
* @return boolean
* @return bool
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::inCakePath
*/
public function inCakePath($path = '') {
@ -319,21 +399,26 @@ class Folder {
}
/**
* Returns true if the File is in given path.
* Returns true if the Folder is in the given path.
*
* @param string $path The path to check that the current pwd() resides with in.
* @param boolean $reverse Reverse the search, check that pwd() resides within $path.
* @return boolean
* @param string $path The absolute path to check that the current `pwd()` resides within.
* @param bool $reverse Reverse the search, check if the given `$path` resides within the current `pwd()`.
* @return bool
* @throws \InvalidArgumentException When the given `$path` argument is not an absolute path.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::inPath
*/
public function inPath($path = '', $reverse = false) {
if (!Folder::isAbsolute($path)) {
throw new InvalidArgumentException(__d('cake_dev', 'The $path argument is expected to be an absolute path.'));
}
$dir = Folder::slashTerm($path);
$current = Folder::slashTerm($this->pwd());
if (!$reverse) {
$return = preg_match('/^(.*)' . preg_quote($dir, '/') . '(.*)/', $current);
$return = preg_match('/^' . preg_quote($dir, '/') . '(.*)/', $current);
} else {
$return = preg_match('/^(.*)' . preg_quote($current, '/') . '(.*)/', $dir);
$return = preg_match('/^' . preg_quote($current, '/') . '(.*)/', $dir);
}
return (bool)$return;
}
@ -341,11 +426,11 @@ class Folder {
/**
* Change the mode on a directory structure recursively. This includes changing the mode on files as well.
*
* @param string $path The path to chmod
* @param integer $mode octal value 0755
* @param boolean $recursive chmod recursively, set to false to only change the current directory.
* @param array $exceptions array of files, directories to skip
* @return boolean Returns TRUE on success, FALSE on failure
* @param string $path The path to chmod.
* @param int $mode Octal value, e.g. 0755.
* @param bool $recursive Chmod recursively, set to false to only change the current directory.
* @param array $exceptions Array of files, directories to skip.
* @return bool Success.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::chmod
*/
public function chmod($path, $mode = false, $recursive = true, $exceptions = array()) {
@ -398,14 +483,14 @@ class Folder {
* Returns an array of nested directories and files in each directory
*
* @param string $path the directory path to build the tree from
* @param array|boolean $exceptions Either an array of files/folder to exclude
* @param array|bool $exceptions Either an array of files/folder to exclude
* or boolean true to not grab dot files/folders
* @param string $type either 'file' or 'dir'. null returns both files and directories
* @return mixed array of nested directories and files in each directory
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::tree
*/
public function tree($path = null, $exceptions = false, $type = null) {
if ($path == null) {
if (!$path) {
$path = $this->path;
}
$files = array();
@ -435,7 +520,7 @@ class Folder {
foreach ($iterator as $itemPath => $fsIterator) {
if ($skipHidden) {
$subPathName = $fsIterator->getSubPathname();
if ($subPathName{0} == '.' || strpos($subPathName, DS . '.') !== false) {
if ($subPathName{0} === '.' || strpos($subPathName, DS . '.') !== false) {
continue;
}
}
@ -460,12 +545,15 @@ class Folder {
}
/**
* Create a directory structure recursively. Can be used to create
* deep path structures like `/foo/bar/baz/shoe/horn`
* Create a directory structure recursively.
*
* @param string $pathname The directory structure to create
* @param integer $mode octal value 0755
* @return boolean Returns TRUE on success, FALSE on failure
* Can be used to create deep path structures like `/foo/bar/baz/shoe/horn`
*
* @param string $pathname The directory structure to create. Either an absolute or relative
* path. If the path is relative and exists in the process' cwd it will not be created.
* Otherwise relative paths will be prefixed with the current pwd().
* @param int $mode octal value 0755
* @return bool Returns TRUE on success, FALSE on failure
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::create
*/
public function create($pathname, $mode = false) {
@ -473,6 +561,10 @@ class Folder {
return true;
}
if (!static::isAbsolute($pathname)) {
$pathname = static::addPathElement($this->pwd(), $pathname);
}
if (!$mode) {
$mode = $this->mode;
}
@ -491,11 +583,10 @@ class Folder {
umask($old);
$this->_messages[] = __d('cake_dev', '%s created', $pathname);
return true;
} else {
umask($old);
$this->_errors[] = __d('cake_dev', '%s NOT created', $pathname);
return false;
}
umask($old);
$this->_errors[] = __d('cake_dev', '%s NOT created', $pathname);
return false;
}
}
return false;
@ -504,7 +595,7 @@ class Folder {
/**
* Returns the size in bytes of this Folder and its contents.
*
* @return integer size in bytes of current folder
* @return int size in bytes of current folder
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::dirsize
*/
public function dirsize() {
@ -541,7 +632,7 @@ class Folder {
* Recursively Remove directories if the system allows.
*
* @param string $path Path of directory to delete
* @return boolean Success
* @return bool Success
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::delete
*/
public function delete($path = null) {
@ -549,7 +640,7 @@ class Folder {
$path = $this->pwd();
}
if (!$path) {
return null;
return false;
}
$path = Folder::slashTerm($path);
if (is_dir($path)) {
@ -602,14 +693,15 @@ class Folder {
*
* - `to` The directory to copy to.
* - `from` The directory to copy from, this will cause a cd() to occur, changing the results of pwd().
* - `mode` The mode to copy the files/directories with.
* - `mode` The mode to copy the files/directories with as integer, e.g. 0775.
* - `skip` Files/directories to skip.
* - `scheme` Folder::MERGE, Folder::OVERWRITE, Folder::SKIP
*
* @param array|string $options Either an array of options (see above) or a string of the destination directory.
* @return boolean Success
* @return bool Success.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::copy
*/
public function copy($options = array()) {
public function copy($options) {
if (!$this->pwd()) {
return false;
}
@ -618,7 +710,13 @@ class Folder {
$to = $options;
$options = array();
}
$options = array_merge(array('to' => $to, 'from' => $this->path, 'mode' => $this->mode, 'skip' => array()), $options);
$options += array(
'to' => $to,
'from' => $this->path,
'mode' => $this->mode,
'skip' => array(),
'scheme' => Folder::MERGE
);
$fromDir = $options['from'];
$toDir = $options['to'];
@ -642,11 +740,11 @@ class Folder {
//@codingStandardsIgnoreStart
if ($handle = @opendir($fromDir)) {
//@codingStandardsIgnoreEnd
while (false !== ($item = readdir($handle))) {
if (!in_array($item, $exceptions)) {
while (($item = readdir($handle)) !== false) {
$to = Folder::addPathElement($toDir, $item);
if (($options['scheme'] != Folder::SKIP || !is_dir($to)) && !in_array($item, $exceptions)) {
$from = Folder::addPathElement($fromDir, $item);
$to = Folder::addPathElement($toDir, $item);
if (is_file($from)) {
if (is_file($from) && (!is_file($to) || $options['scheme'] != Folder::SKIP)) {
if (copy($from, $to)) {
chmod($to, intval($mode, 8));
touch($to, filemtime($from));
@ -656,6 +754,10 @@ class Folder {
}
}
if (is_dir($from) && file_exists($to) && $options['scheme'] === Folder::OVERWRITE) {
$this->delete($to);
}
if (is_dir($from) && !file_exists($to)) {
$old = umask(0);
if (mkdir($to, $mode)) {
@ -664,11 +766,14 @@ class Folder {
chmod($to, $mode);
umask($old);
$this->_messages[] = __d('cake_dev', '%s created', $to);
$options = array_merge($options, array('to' => $to, 'from' => $from));
$options = array('to' => $to, 'from' => $from) + $options;
$this->copy($options);
} else {
$this->_errors[] = __d('cake_dev', '%s not created', $to);
}
} elseif (is_dir($from) && $options['scheme'] === Folder::MERGE) {
$options = array('to' => $to, 'from' => $from) + $options;
$this->copy($options);
}
}
}
@ -692,9 +797,10 @@ class Folder {
* - `from` The directory to copy from, this will cause a cd() to occur, changing the results of pwd().
* - `chmod` The mode to copy the files/directories with.
* - `skip` Files/directories to skip.
* - `scheme` Folder::MERGE, Folder::OVERWRITE, Folder::SKIP
*
* @param array $options (to, from, chmod, skip)
* @return boolean Success
* @param array $options (to, from, chmod, skip, scheme)
* @return bool Success
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::move
*/
public function move($options) {
@ -703,10 +809,7 @@ class Folder {
$to = $options;
$options = (array)$options;
}
$options = array_merge(
array('to' => $to, 'from' => $this->path, 'mode' => $this->mode, 'skip' => array()),
$options
);
$options += array('to' => $to, 'from' => $this->path, 'mode' => $this->mode, 'skip' => array());
if ($this->copy($options)) {
if ($this->delete($options['from'])) {
@ -719,21 +822,31 @@ class Folder {
/**
* get messages from latest method
*
* @param bool $reset Reset message stack after reading
* @return array
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::messages
*/
public function messages() {
return $this->_messages;
public function messages($reset = true) {
$messages = $this->_messages;
if ($reset) {
$this->_messages = array();
}
return $messages;
}
/**
* get error from latest method
*
* @param bool $reset Reset error stack after reading
* @return array
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::errors
*/
public function errors() {
return $this->_errors;
public function errors($reset = true) {
$errors = $this->_errors;
if ($reset) {
$this->_errors = array();
}
return $errors;
}
/**
@ -744,13 +857,13 @@ class Folder {
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::realpath
*/
public function realpath($path) {
$path = str_replace('/', DS, trim($path));
if (strpos($path, '..') === false) {
if (!Folder::isAbsolute($path)) {
$path = Folder::addPathElement($this->path, $path);
}
return $path;
}
$path = str_replace('/', DS, trim($path));
$parts = explode(DS, $path);
$newparts = array();
$newpath = '';
@ -766,9 +879,8 @@ class Folder {
if (!empty($newparts)) {
array_pop($newparts);
continue;
} else {
return false;
}
return false;
}
$newparts[] = $part;
}
@ -781,7 +893,7 @@ class Folder {
* Returns true if given $path ends in a slash (i.e. is slash-terminated).
*
* @param string $path Path to check
* @return boolean true if path ends with slash, false otherwise
* @return bool true if path ends with slash, false otherwise
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::isSlashTerm
*/
public static function isSlashTerm($path) {

View file

@ -1,26 +1,27 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Utility
* @since CakePHP(tm) v 2.2.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
App::uses('String', 'Utility');
App::uses('CakeText', 'Utility');
/**
* Library of array functions for manipulating and extracting data
* from arrays or 'sets' of data.
*
* `Hash` provides an improved interface, more consistent and
* predictable set of features over `Set`. While it lacks the spotty
* predictable set of features over `Set`. While it lacks the spotty
* support for pseudo Xpath, its more fully featured dot notation provides
* similar features in a more consistent implementation.
*
@ -36,25 +37,35 @@ class Hash {
* @param array $data Array of data to operate on.
* @param string|array $path The path being searched for. Either a dot
* separated string, or an array of path segments.
* @param mixed $default The return value when the path does not exist
* @throws InvalidArgumentException
* @return mixed The value fetched from the array, or null.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::get
*/
public static function get(array $data, $path) {
if (empty($data) || empty($path)) {
return null;
public static function get(array $data, $path, $default = null) {
if (empty($data) || $path === null) {
return $default;
}
if (is_string($path)) {
if (is_string($path) || is_numeric($path)) {
$parts = explode('.', $path);
} else {
if (!is_array($path)) {
throw new InvalidArgumentException(__d('cake_dev',
'Invalid Parameter %s, should be dot separated path or array.',
$path
));
}
$parts = $path;
}
foreach ($parts as $key) {
if (is_array($data) && isset($data[$key])) {
$data =& $data[$key];
} else {
return null;
return $default;
}
}
return $data;
}
@ -65,6 +76,7 @@ class Hash {
*
* - `{n}` Matches any numeric key, or integer.
* - `{s}` Matches any string key.
* - `{*}` Matches any value.
* - `Foo` Matches any key with the exact same value.
*
* There are a number of attribute operators:
@ -83,8 +95,9 @@ class Hash {
*
* @param array $data The data to extract from.
* @param string $path The path to extract.
* @return array An array of the extracted values. Returns an empty array
* @return array An array of the extracted values. Returns an empty array
* if there are no matches.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::extract
*/
public static function extract(array $data, $path) {
if (empty($path)) {
@ -93,13 +106,13 @@ class Hash {
// Simple paths.
if (!preg_match('/[{\[]/', $path)) {
return (array)self::get($data, $path);
return (array)static::get($data, $path);
}
if (strpos($path, '[') === false) {
$tokens = explode('.', $path);
} else {
$tokens = String::tokenize($path, '.', '[', ']');
$tokens = CakeText::tokenize($path, '.', '[', ']');
}
$_key = '__set_item__';
@ -109,16 +122,11 @@ class Hash {
foreach ($tokens as $token) {
$next = array();
$conditions = false;
$position = strpos($token, '[');
if ($position !== false) {
$conditions = substr($token, $position);
$token = substr($token, 0, $position);
}
list($token, $conditions) = static::_splitConditions($token);
foreach ($context[$_key] as $item) {
foreach ($item as $k => $v) {
if (self::_matchToken($k, $token)) {
foreach ((array)$item as $k => $v) {
if (static::_matchToken($k, $token)) {
$next[] = $v;
}
}
@ -128,7 +136,7 @@ class Hash {
if ($conditions) {
$filter = array();
foreach ($next as $item) {
if (self::_matches($item, $conditions)) {
if (is_array($item) && static::_matches($item, $conditions)) {
$filter[] = $item;
}
}
@ -139,25 +147,41 @@ class Hash {
}
return $context[$_key];
}
/**
* Split token conditions
*
* @param string $token the token being splitted.
* @return array array(token, conditions) with token splitted
*/
protected static function _splitConditions($token) {
$conditions = false;
$position = strpos($token, '[');
if ($position !== false) {
$conditions = substr($token, $position);
$token = substr($token, 0, $position);
}
return array($token, $conditions);
}
/**
* Check a key against a token.
*
* @param string $key The key in the array being searched.
* @param string $token The token being matched.
* @return boolean
* @return bool
*/
protected static function _matchToken($key, $token) {
if ($token === '{n}') {
return is_numeric($key);
switch ($token) {
case '{n}':
return is_numeric($key);
case '{s}':
return is_string($key);
case '{*}':
return true;
default:
return is_numeric($token) ? ($key == $token) : $key === $token;
}
if ($token === '{s}') {
return is_string($key);
}
if (is_numeric($token)) {
return ($key == $token);
}
return ($key === $token);
}
/**
@ -165,11 +189,11 @@ class Hash {
*
* @param array $data Array of data to match.
* @param string $selector The patterns to match.
* @return boolean Fitness of expression.
* @return bool Fitness of expression.
*/
protected static function _matches(array $data, $selector) {
preg_match_all(
'/(\[ (?<attr>[^=><!]+?) (\s* (?<op>[><!]?[=]|[><]) \s* (?<val>[^\]]+) )? \])/x',
'/(\[ (?P<attr>[^=><!]+?) (\s* (?P<op>[><!]?[=]|[><]) \s* (?P<val>(?:\/.*?\/ | [^\]]+)) )? \])/x',
$selector,
$conditions,
PREG_SET_ORDER
@ -190,15 +214,23 @@ class Hash {
return false;
}
$prop = isset($data[$attr]) ? $data[$attr] : null;
$prop = null;
if (isset($data[$attr])) {
$prop = $data[$attr];
}
$isBool = is_bool($prop);
if ($isBool && is_numeric($val)) {
$prop = $prop ? '1' : '0';
} elseif ($isBool) {
$prop = $prop ? 'true' : 'false';
}
// Pattern matches and other operators.
if ($op === '=' && $val && $val[0] === '/') {
if (!preg_match($val, $prop)) {
return false;
}
} elseif (
($op === '=' && $prop != $val) ||
} elseif (($op === '=' && $prop != $val) ||
($op === '!=' && $prop == $val) ||
($op === '>' && $prop <= $val) ||
($op === '<' && $prop >= $val) ||
@ -218,20 +250,35 @@ class Hash {
*
* @param array $data The data to insert into.
* @param string $path The path to insert at.
* @param array $values The values to insert.
* @param mixed $values The values to insert.
* @return array The data with $values inserted.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::insert
*/
public static function insert(array $data, $path, $values = null) {
$tokens = explode('.', $path);
if (strpos($path, '{') === false) {
return self::_simpleOp('insert', $data, $tokens, $values);
if (strpos($path, '[') === false) {
$tokens = explode('.', $path);
} else {
$tokens = CakeText::tokenize($path, '.', '[', ']');
}
if (strpos($path, '{') === false && strpos($path, '[') === false) {
return static::_simpleOp('insert', $data, $tokens, $values);
}
$token = array_shift($tokens);
$nextPath = implode('.', $tokens);
list($token, $conditions) = static::_splitConditions($token);
foreach ($data as $k => $v) {
if (self::_matchToken($k, $token)) {
$data[$k] = self::insert($v, $nextPath, $values);
if (static::_matchToken($k, $token)) {
if ($conditions && static::_matches($v, $conditions)) {
$data[$k] = array_merge($v, $values);
continue;
}
if (!$conditions) {
$data[$k] = static::insert($v, $nextPath, $values);
}
}
}
return $data;
@ -244,7 +291,7 @@ class Hash {
* @param array $data The data to operate on.
* @param array $path The path to work on.
* @param mixed $values The values to insert when doing inserts.
* @return array $data.
* @return array data.
*/
protected static function _simpleOp($op, $data, $path, $values = null) {
$_list =& $data;
@ -252,8 +299,8 @@ class Hash {
$count = count($path);
$last = $count - 1;
foreach ($path as $i => $key) {
if (is_numeric($key) && intval($key) > 0 || $key === '0') {
$key = intval($key);
if ((is_numeric($key) && intval($key) > 0 || $key === '0') && strpos($key, '0') !== 0) {
$key = (int)$key;
}
if ($op === 'insert') {
if ($i === $last) {
@ -288,20 +335,36 @@ class Hash {
* @param array $data The data to operate on
* @param string $path A path expression to use to remove.
* @return array The modified array.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::remove
*/
public static function remove(array $data, $path) {
$tokens = explode('.', $path);
if (strpos($path, '{') === false) {
return self::_simpleOp('remove', $data, $tokens);
if (strpos($path, '[') === false) {
$tokens = explode('.', $path);
} else {
$tokens = CakeText::tokenize($path, '.', '[', ']');
}
if (strpos($path, '{') === false && strpos($path, '[') === false) {
return static::_simpleOp('remove', $data, $tokens);
}
$token = array_shift($tokens);
$nextPath = implode('.', $tokens);
list($token, $conditions) = static::_splitConditions($token);
foreach ($data as $k => $v) {
$match = self::_matchToken($k, $token);
$match = static::_matchToken($k, $token);
if ($match && is_array($v)) {
$data[$k] = self::remove($v, $nextPath);
} elseif ($match) {
if ($conditions && static::_matches($v, $conditions)) {
unset($data[$k]);
continue;
}
$data[$k] = static::remove($v, $nextPath);
if (empty($data[$k])) {
unset($data[$k]);
}
} elseif ($match && empty($nextPath)) {
unset($data[$k]);
}
}
@ -320,6 +383,7 @@ class Hash {
* @param string $groupPath A dot-separated string.
* @return array Combined array
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::combine
* @throws CakeException CakeException When keys and values count is unequal.
*/
public static function combine(array $data, $keyPath, $valuePath = null, $groupPath = null) {
if (empty($data)) {
@ -328,9 +392,9 @@ class Hash {
if (is_array($keyPath)) {
$format = array_shift($keyPath);
$keys = self::format($data, $keyPath, $format);
$keys = static::format($data, $keyPath, $format);
} else {
$keys = self::extract($data, $keyPath);
$keys = static::extract($data, $keyPath);
}
if (empty($keys)) {
return array();
@ -338,18 +402,23 @@ class Hash {
if (!empty($valuePath) && is_array($valuePath)) {
$format = array_shift($valuePath);
$vals = self::format($data, $valuePath, $format);
$vals = static::format($data, $valuePath, $format);
} elseif (!empty($valuePath)) {
$vals = self::extract($data, $valuePath);
$vals = static::extract($data, $valuePath);
}
if (empty($vals)) {
$vals = array_fill(0, count($keys), null);
}
$count = count($keys);
for ($i = 0; $i < $count; $i++) {
$vals[$i] = isset($vals[$i]) ? $vals[$i] : null;
if (count($keys) !== count($vals)) {
throw new CakeException(__d(
'cake_dev',
'Hash::combine() needs an equal number of keys + values.'
));
}
if ($groupPath !== null) {
$group = self::extract($data, $groupPath);
$group = static::extract($data, $groupPath);
if (!empty($group)) {
$c = count($keys);
for ($i = 0; $i < $c; $i++) {
@ -371,14 +440,14 @@ class Hash {
}
/**
* Returns a formated series of values extracted from `$data`, using
* Returns a formatted series of values extracted from `$data`, using
* `$format` as the format and `$paths` as the values to extract.
*
* Usage:
*
* {{{
* ```
* $result = Hash::format($users, array('{n}.User.id', '{n}.User.name'), '%s : %s');
* }}}
* ```
*
* The `$format` string can use any format options that `vsprintf()` and `sprintf()` do.
*
@ -389,17 +458,18 @@ class Hash {
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::format
* @see sprintf()
* @see Hash::extract()
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::format
*/
public static function format(array $data, array $paths, $format) {
$extracted = array();
$count = count($paths);
if (!$count) {
return;
return null;
}
for ($i = 0; $i < $count; $i++) {
$extracted[] = self::extract($data, $paths[$i]);
$extracted[] = static::extract($data, $paths[$i]);
}
$out = array();
$data = $extracted;
@ -423,7 +493,7 @@ class Hash {
*
* @param array $data The data to search through.
* @param array $needle The values to file in $data
* @return boolean true if $data contains $needle, false otherwise
* @return bool true if $data contains $needle, false otherwise
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::contains
*/
public static function contains(array $data, array $needle) {
@ -437,14 +507,14 @@ class Hash {
$val = $needle[$key];
unset($needle[$key]);
if (isset($data[$key]) && is_array($val)) {
if (array_key_exists($key, $data) && is_array($val)) {
$next = $data[$key];
unset($data[$key]);
if (!empty($val)) {
$stack[] = array($val, $next);
}
} elseif (!isset($data[$key]) || $data[$key] != $val) {
} elseif (!array_key_exists($key, $data) || $data[$key] != $val) {
return false;
}
@ -464,11 +534,12 @@ class Hash {
*
* @param array $data The data to check.
* @param string $path The path to check for.
* @return boolean Existence of path.
* @return bool Existence of path.
* @see Hash::extract()
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::check
*/
public static function check(array $data, $path) {
$results = self::extract($data, $path);
$results = static::extract($data, $path);
if (!is_array($results)) {
return false;
}
@ -479,15 +550,15 @@ class Hash {
* Recursively filters a data set.
*
* @param array $data Either an array to filter, or value when in callback
* @param callable $callback A function to filter the data with. Defaults to
* `self::_filter()` Which strips out all non-zero empty values.
* @param callable $callback A function to filter the data with. Defaults to
* `static::_filter()` Which strips out all non-zero empty values.
* @return array Filtered array
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::filter
*/
public static function filter(array $data, $callback = array('self', '_filter')) {
foreach ($data as $k => $v) {
if (is_array($v)) {
$data[$k] = self::filter($v, $callback);
$data[$k] = static::filter($v, $callback);
}
}
return array_filter($data, $callback);
@ -497,7 +568,7 @@ class Hash {
* Callback function for filtering.
*
* @param array $var Array to filter.
* @return boolean
* @return bool
*/
protected static function _filter($var) {
if ($var === 0 || $var === '0' || !empty($var)) {
@ -547,7 +618,7 @@ class Hash {
}
/**
* Expand/unflattens an string to an array
* Expands a flat array to a nested array.
*
* For example, unflattens an array that was collapsed with `Hash::flatten()`
* into a multi-dimensional array. So, `array('0.Foo.Bar' => 'Far')` becomes
@ -556,9 +627,13 @@ class Hash {
* @param array $data Flattened array
* @param string $separator The delimiter used
* @return array
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::expand
*/
public static function expand($data, $separator = '.') {
$result = array();
$stack = array();
foreach ($data as $flat => $value) {
$keys = explode($separator, $flat);
$keys = array_reverse($keys);
@ -571,7 +646,24 @@ class Hash {
$k => $child
);
}
$result = self::merge($result, $child);
$stack[] = array($child, &$result);
while (!empty($stack)) {
foreach ($stack as $curKey => &$curMerge) {
foreach ($curMerge[0] as $key => &$val) {
if (!empty($curMerge[1][$key]) && (array)$curMerge[1][$key] === $curMerge[1][$key] && (array)$val === $val) {
$stack[] = array(&$val, &$curMerge[1][$key]);
} elseif ((int)$key === $key && isset($curMerge[1][$key])) {
$curMerge[1][] = $val;
} else {
$curMerge[1][$key] = $val;
}
}
unset($stack[$curKey]);
}
unset($curMerge);
}
}
return $result;
}
@ -580,7 +672,7 @@ class Hash {
* This function can be thought of as a hybrid between PHP's `array_merge` and `array_merge_recursive`.
*
* The difference between this method and the built-in ones, is that if an array key contains another array, then
* Hash::merge() will behave in a recursive fashion (unlike `array_merge`). But it will not act recursively for
* Hash::merge() will behave in a recursive fashion (unlike `array_merge`). But it will not act recursively for
* keys that contain scalar values (unlike `array_merge_recursive`).
*
* Note: This function will work with an unlimited amount of arguments and typecasts non-array parameters into arrays.
@ -591,19 +683,28 @@ class Hash {
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::merge
*/
public static function merge(array $data, $merge) {
$args = func_get_args();
$return = current($args);
$args = array_slice(func_get_args(), 1);
$return = $data;
while (($arg = next($args)) !== false) {
foreach ((array)$arg as $key => $val) {
if (!empty($return[$key]) && is_array($return[$key]) && is_array($val)) {
$return[$key] = self::merge($return[$key], $val);
} elseif (is_int($key)) {
$return[] = $val;
} else {
$return[$key] = $val;
foreach ($args as &$curArg) {
$stack[] = array((array)$curArg, &$return);
}
unset($curArg);
while (!empty($stack)) {
foreach ($stack as $curKey => &$curMerge) {
foreach ($curMerge[0] as $key => &$val) {
if (!empty($curMerge[1][$key]) && (array)$curMerge[1][$key] === $curMerge[1][$key] && (array)$val === $val) {
$stack[] = array(&$val, &$curMerge[1][$key]);
} elseif ((int)$key === $key && isset($curMerge[1][$key])) {
$curMerge[1][] = $val;
} else {
$curMerge[1][$key] = $val;
}
}
unset($stack[$curKey]);
}
unset($curMerge);
}
return $return;
}
@ -611,28 +712,26 @@ class Hash {
/**
* Checks to see if all the values in the array are numeric
*
* @param array $array The array to check.
* @return boolean true if values are numeric, false otherwise
* @param array $data The array to check.
* @return bool true if values are numeric, false otherwise
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::numeric
*/
public static function numeric(array $data) {
if (empty($data)) {
return false;
}
$values = array_values($data);
$str = implode('', $values);
return (bool)ctype_digit($str);
return $data === array_filter($data, 'is_numeric');
}
/**
* Counts the dimensions of an array.
* Only considers the dimension of the first element in the array.
*
* If you have an un-even or hetrogenous array, consider using Hash::maxDimensions()
* If you have an un-even or heterogenous array, consider using Hash::maxDimensions()
* to get the dimensions of the array.
*
* @param array $array Array to count dimensions on
* @return integer The number of dimensions in $data
* @param array $data Array to count dimensions on
* @return int The number of dimensions in $data
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::dimensions
*/
public static function dimensions(array $data) {
@ -657,17 +756,17 @@ class Hash {
* number of dimensions in a mixed array.
*
* @param array $data Array to count dimensions on
* @return integer The maximum number of dimensions in $data
* @return int The maximum number of dimensions in $data
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::maxDimensions
*/
public static function maxDimensions(array $data) {
public static function maxDimensions($data) {
$depth = array();
if (is_array($data) && reset($data) !== false) {
foreach ($data as $value) {
$depth[] = self::dimensions((array)$value) + 1;
$depth[] = static::maxDimensions($value) + 1;
}
}
return max($depth);
return empty($depth) ? 0 : max($depth);
}
/**
@ -678,9 +777,10 @@ class Hash {
* @param string $path The path to extract for mapping over.
* @param callable $function The function to call on each extracted value.
* @return array An array of the modified values.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::map
*/
public static function map(array $data, $path, $function) {
$values = (array)self::extract($data, $path);
$values = (array)static::extract($data, $path);
return array_map($function, $values);
}
@ -689,10 +789,12 @@ class Hash {
*
* @param array $data The data to reduce.
* @param string $path The path to extract from $data.
* @param callable $function The function to call on each extracted value.
* @return mixed The reduced value.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::reduce
*/
public static function reduce(array $data, $path, $function) {
$values = (array)self::extract($data, $path);
$values = (array)static::extract($data, $path);
return array_reduce($values, $function);
}
@ -713,15 +815,16 @@ class Hash {
*
* @param array $data The data to reduce.
* @param string $path The path to extract from $data.
* @param callable $function The function to call on each extracted value.
* @return mixed The results of the applied method.
*/
public static function apply(array $data, $path, $function) {
$values = (array)self::extract($data, $path);
$values = (array)static::extract($data, $path);
return call_user_func($function, $values);
}
/**
* Sorts an array by any value, determined by a Set-compatible path
* Sorts an array by any value, determined by a Hash-compatible path
*
* ### Sort directions
*
@ -730,19 +833,30 @@ class Hash {
*
* ## Sort types
*
* - `numeric` Sort by numeric value.
* - `regular` Sort by numeric value.
* - `string` Sort by numeric value.
* - `natural` Sort by natural order. Requires PHP 5.4 or greater.
* - `regular` For regular sorting (don't change types)
* - `numeric` Compare values numerically
* - `string` Compare values as strings
* - `locale` Compare items as strings, based on the current locale
* - `natural` Compare items as strings using "natural ordering" in a human friendly way.
* Will sort foo10 below foo2 as an example. Requires PHP 5.4 or greater or it will fallback to 'regular'
*
* To do case insensitive sorting, pass the type as an array as follows:
*
* ```
* array('type' => 'regular', 'ignoreCase' => true)
* ```
*
* When using the array form, `type` defaults to 'regular'. The `ignoreCase` option
* defaults to `false`.
*
* @param array $data An array of data to sort
* @param string $path A Set-compatible path to the array value
* @param string $dir See directions above.
* @param string $type See direction types above. Defaults to 'regular'.
* @param string $path A Hash-compatible path to the array value
* @param string $dir See directions above. Defaults to 'asc'.
* @param array|string $type See direction types above. Defaults to 'regular'.
* @return array Sorted array of data
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::sort
*/
public static function sort(array $data, $path, $dir, $type = 'regular') {
public static function sort(array $data, $path, $dir = 'asc', $type = 'regular') {
if (empty($data)) {
return array();
}
@ -751,7 +865,7 @@ class Hash {
if ($numeric) {
$data = array_values($data);
}
$sortValues = self::extract($data, $path);
$sortValues = static::extract($data, $path);
$sortCount = count($sortValues);
$dataCount = count($data);
@ -760,15 +874,26 @@ class Hash {
if ($sortCount < $dataCount) {
$sortValues = array_pad($sortValues, $dataCount, null);
}
$result = self::_squash($sortValues);
$keys = self::extract($result, '{n}.id');
$values = self::extract($result, '{n}.value');
$result = static::_squash($sortValues);
$keys = static::extract($result, '{n}.id');
$values = static::extract($result, '{n}.value');
$dir = strtolower($dir);
$type = strtolower($type);
if ($type == 'natural' && version_compare(PHP_VERSION, '5.4.0', '<')) {
$type == 'regular';
$ignoreCase = false;
// $type can be overloaded for case insensitive sort
if (is_array($type)) {
$type += array('ignoreCase' => false, 'type' => 'regular');
$ignoreCase = $type['ignoreCase'];
$type = $type['type'];
} else {
$type = strtolower($type);
}
if ($type === 'natural' && version_compare(PHP_VERSION, '5.4.0', '<')) {
$type = 'regular';
}
if ($dir === 'asc') {
$dir = SORT_ASC;
} else {
@ -780,10 +905,17 @@ class Hash {
$type = SORT_STRING;
} elseif ($type === 'natural') {
$type = SORT_NATURAL;
} elseif ($type === 'locale') {
$type = SORT_LOCALE_STRING;
} else {
$type = SORT_REGULAR;
}
array_multisort($values, $dir, $type, $keys, $dir, $type);
if ($ignoreCase) {
$values = array_map('mb_strtolower', $values);
}
array_multisort($values, $dir, $type, $keys, $dir);
$sorted = array();
$keys = array_unique($keys);
@ -803,7 +935,7 @@ class Hash {
/**
* Helper method for sort()
* Sqaushes an array to a single hash so it can be sorted.
* Squashes an array to a single hash so it can be sorted.
*
* @param array $data The data to squash.
* @param string $key The key for the data.
@ -813,11 +945,11 @@ class Hash {
$stack = array();
foreach ($data as $k => $r) {
$id = $k;
if (!is_null($key)) {
if ($key !== null) {
$id = $key;
}
if (is_array($r) && !empty($r)) {
$stack = array_merge($stack, self::_squash($r, $id));
$stack = array_merge($stack, static::_squash($r, $id));
} else {
$stack[] = array('id' => $id, 'value' => $r);
}
@ -855,11 +987,12 @@ class Hash {
}
/**
* Merges the difference between $data and $push onto $data.
* Merges the difference between $data and $compare onto $data.
*
* @param array $data The data to append onto.
* @param array $compare The data to compare and append onto.
* @return array The merged array.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::mergeDiff
*/
public static function mergeDiff(array $data, $compare) {
if (empty($data) && !empty($compare)) {
@ -872,7 +1005,7 @@ class Hash {
if (!array_key_exists($key, $data)) {
$data[$key] = $value;
} elseif (is_array($value)) {
$data[$key] = self::mergeDiff($data[$key], $compare[$key]);
$data[$key] = static::mergeDiff($data[$key], $compare[$key]);
}
}
return $data;
@ -882,7 +1015,7 @@ class Hash {
* Normalizes an array, and converts it to a standard format.
*
* @param array $data List to normalize
* @param boolean $assoc If true, $data will be converted to an associative array.
* @param bool $assoc If true, $data will be converted to an associative array.
* @return array
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::normalize
*/
@ -929,6 +1062,8 @@ class Hash {
* @param array $options Options are:
* @return array of results, nested
* @see Hash::extract()
* @throws InvalidArgumentException When providing invalid data.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::nest
*/
public static function nest(array $data, $options = array()) {
if (!$data) {
@ -944,7 +1079,7 @@ class Hash {
);
$return = $idMap = array();
$ids = self::extract($data, $options['idPath']);
$ids = static::extract($data, $options['idPath']);
$idKeys = explode('.', $options['idPath']);
array_shift($idKeys);
@ -955,8 +1090,8 @@ class Hash {
foreach ($data as $result) {
$result[$options['children']] = array();
$id = self::get($result, $idKeys);
$parentId = self::get($result, $parentKeys);
$id = static::get($result, $idKeys);
$parentId = static::get($result, $parentKeys);
if (isset($idMap[$id][$options['children']])) {
$idMap[$id] = array_merge($result, (array)$idMap[$id]);
@ -970,15 +1105,21 @@ class Hash {
}
}
if (!$return) {
throw new InvalidArgumentException(__d('cake_dev',
'Invalid data array to nest.'
));
}
if ($options['root']) {
$root = $options['root'];
} else {
$root = self::get($return[0], $parentKeys);
$root = static::get($return[0], $parentKeys);
}
foreach ($return as $i => $result) {
$id = self::get($result, $idKeys);
$parentId = self::get($result, $parentKeys);
$id = static::get($result, $idKeys);
$parentId = static::get($result, $parentKeys);
if ($id !== $root && $parentId != $root) {
unset($return[$i]);
}

View file

@ -1,23 +1,24 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Utility
* @since CakePHP(tm) v 0.2.9
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* Pluralize and singularize English words.
*
* Inflector pluralizes and singularizes English nouns.
* Used by Cake's naming conventions throughout the framework.
* Used by CakePHP's naming conventions throughout the framework.
*
* @package Cake.Utility
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/inflector.html
@ -31,7 +32,7 @@ class Inflector {
*/
protected static $_plural = array(
'rules' => array(
'/(s)tatus$/i' => '\1\2tatuses',
'/(s)tatus$/i' => '\1tatuses',
'/(quiz)$/i' => '\1zes',
'/^(ox)$/i' => '\1\2en',
'/([m|l])ouse$/i' => '\1ice',
@ -39,14 +40,14 @@ class Inflector {
'/(x|ch|ss|sh)$/i' => '\1es',
'/([^aeiouy]|qu)y$/i' => '\1ies',
'/(hive)$/i' => '\1s',
'/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
'/(?:([^f])fe|([lre])f)$/i' => '\1\2ves',
'/sis$/i' => 'ses',
'/([ti])um$/i' => '\1a',
'/(p)erson$/i' => '\1eople',
'/(m)an$/i' => '\1en',
'/(?<!u)(m)an$/i' => '\1en',
'/(c)hild$/i' => '\1hildren',
'/(buffal|tomat)o$/i' => '\1\2oes',
'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i',
'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin)us$/i' => '\1i',
'/us$/i' => 'uses',
'/(alias)$/i' => '\1es',
'/(ax|cris|test)is$/i' => '\1es',
@ -55,17 +56,29 @@ class Inflector {
'/$/' => 's',
),
'uninflected' => array(
'.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', 'people', 'cookie'
'.*[nrlm]ese',
'.*data',
'.*deer',
'.*fish',
'.*measles',
'.*ois',
'.*pox',
'.*sheep',
'people',
'feedback',
'stadia'
),
'irregular' => array(
'atlas' => 'atlases',
'beef' => 'beefs',
'brief' => 'briefs',
'brother' => 'brothers',
'cafe' => 'cafes',
'child' => 'children',
'cookie' => 'cookies',
'corpus' => 'corpuses',
'cow' => 'cows',
'criterion' => 'criteria',
'ganglion' => 'ganglions',
'genie' => 'genies',
'genus' => 'genera',
@ -89,7 +102,13 @@ class Inflector {
'soliloquy' => 'soliloquies',
'testis' => 'testes',
'trilby' => 'trilbys',
'turf' => 'turfs'
'turf' => 'turfs',
'potato' => 'potatoes',
'hero' => 'heroes',
'tooth' => 'teeth',
'goose' => 'geese',
'foot' => 'feet',
'sieve' => 'sieves'
)
);
@ -110,7 +129,7 @@ class Inflector {
'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us',
'/([ftw]ax)es/i' => '\1',
'/(cris|ax|test)es$/i' => '\1is',
'/(shoe|slave)s$/i' => '\1',
'/(shoe)s$/i' => '\1',
'/(o)es$/i' => '\1',
'/ouses$/' => 'ouse',
'/([^a])uses$/' => '\1us',
@ -119,11 +138,11 @@ class Inflector {
'/(m)ovies$/i' => '\1\2ovie',
'/(s)eries$/i' => '\1\2eries',
'/([^aeiouy]|qu)ies$/i' => '\1y',
'/([lr])ves$/i' => '\1f',
'/(tive)s$/i' => '\1',
'/(hive)s$/i' => '\1',
'/(drive)s$/i' => '\1',
'/([^fo])ves$/i' => '\1fe',
'/([le])ves$/i' => '\1f',
'/([^rfoa])ves$/i' => '\1fe',
'/(^analy)ses$/i' => '\1sis',
'/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
'/([ti])a$/i' => '\1um',
@ -136,12 +155,11 @@ class Inflector {
'/s$/i' => ''
),
'uninflected' => array(
'.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', '.*ss'
'.*data',
'.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', '.*ss', 'feedback'
),
'irregular' => array(
'foes' => 'foe',
'waves' => 'wave',
'curves' => 'curve'
)
);
@ -159,7 +177,7 @@ class Inflector {
'jackanapes', 'Kiplingese', 'Kongoese', 'Lucchese', 'mackerel', 'Maltese', '.*?media',
'mews', 'moose', 'mumps', 'Nankingese', 'news', 'nexus', 'Niasese',
'Pekingese', 'Piedmontese', 'pincers', 'Pistoiese', 'pliers', 'Portuguese',
'proceedings', 'rabies', 'rice', 'rhinoceros', 'salmon', 'Sarawakese', 'scissors',
'proceedings', 'rabies', 'research', 'rice', 'rhinoceros', 'salmon', 'Sarawakese', 'scissors',
'sea[- ]bass', 'series', 'Shavese', 'shears', 'siemens', 'species', 'swine', 'testes',
'trousers', 'trout', 'tuna', 'Vermontese', 'Wenchowese', 'whiting', 'wildebeest',
'Yengeese'
@ -171,56 +189,63 @@ class Inflector {
* @var array
*/
protected static $_transliteration = array(
'/ä|æ|ǽ/' => 'ae',
'/ö|œ/' => 'oe',
'/ü/' => 'ue',
'/Ä/' => 'Ae',
'/Ü/' => 'Ue',
'/Ö/' => 'Oe',
'/À|Á|Â|Ã|Å|Ǻ|Ā|Ă|Ą|Ǎ/' => 'A',
'/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª/' => 'a',
'/Ç|Ć|Ĉ|Ċ|Č/' => 'C',
'/ç|ć|ĉ|ċ|č/' => 'c',
'/Ð|Ď|Đ/' => 'D',
'/ð|ď|đ/' => 'd',
'/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě/' => 'E',
'/è|é|ê|ë|ē|ĕ|ė|ę|ě/' => 'e',
'/Ĝ|Ğ|Ġ|Ģ/' => 'G',
'/ĝ|ğ|ġ|ģ/' => 'g',
'/Ĥ|Ħ/' => 'H',
'/ĥ|ħ/' => 'h',
'/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ/' => 'I',
'/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı/' => 'i',
'/Ĵ/' => 'J',
'/ĵ/' => 'j',
'/Ķ/' => 'K',
'/ķ/' => 'k',
'/Ĺ|Ļ|Ľ|Ŀ|Ł/' => 'L',
'/ĺ|ļ|ľ|ŀ|ł/' => 'l',
'/Ñ|Ń|Ņ|Ň/' => 'N',
'/ñ|ń|ņ|ň|ʼn/' => 'n',
'/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ/' => 'O',
'/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º/' => 'o',
'/Ŕ|Ŗ|Ř/' => 'R',
'/ŕ|ŗ|ř/' => 'r',
'/Ś|Ŝ|Ş|Š/' => 'S',
'/ś|ŝ|ş|š|ſ/' => 's',
'/Ţ|Ť|Ŧ/' => 'T',
'/ţ|ť|ŧ/' => 't',
'/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ/' => 'U',
'/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ/' => 'u',
'/Ý|Ÿ|Ŷ/' => 'Y',
'/ý|ÿ|ŷ/' => 'y',
'/Ŵ/' => 'W',
'/ŵ/' => 'w',
'/Ź|Ż|Ž/' => 'Z',
'/ź|ż|ž/' => 'z',
'/Æ|Ǽ/' => 'AE',
'/ß/' => 'ss',
'/Ä/' => 'Ae',
'/Ç|Ć|Ĉ|Ċ|Č/' => 'C',
'/Ð|Ď|Đ/' => 'D',
'/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě/' => 'E',
'/Ĝ|Ğ|Ġ|Ģ|Ґ/' => 'G',
'/Ĥ|Ħ/' => 'H',
'/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ|І/' => 'I',
'/IJ/' => 'IJ',
'/ij/' => 'ij',
'/Ĵ/' => 'J',
'/Ķ/' => 'K',
'/Ĺ|Ļ|Ľ|Ŀ|Ł/' => 'L',
'/Ñ|Ń|Ņ|Ň/' => 'N',
'/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ/' => 'O',
'/Œ/' => 'OE',
'/ƒ/' => 'f'
'/Ö/' => 'Oe',
'/Ŕ|Ŗ|Ř/' => 'R',
'/Ś|Ŝ|Ş|Ș|Š/' => 'S',
'/ẞ/' => 'SS',
'/Ţ|Ț|Ť|Ŧ/' => 'T',
'/Þ/' => 'TH',
'/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ/' => 'U',
'/Ü/' => 'Ue',
'/Ŵ/' => 'W',
'/Ý|Ÿ|Ŷ/' => 'Y',
'/Є/' => 'Ye',
'/Ї/' => 'Yi',
'/Ź|Ż|Ž/' => 'Z',
'/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª/' => 'a',
'/ä|æ|ǽ/' => 'ae',
'/ç|ć|ĉ|ċ|č/' => 'c',
'/ð|ď|đ/' => 'd',
'/è|é|ê|ë|ē|ĕ|ė|ę|ě/' => 'e',
'/ƒ/' => 'f',
'/ĝ|ğ|ġ|ģ|ґ/' => 'g',
'/ĥ|ħ/' => 'h',
'/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı|і/' => 'i',
'/ij/' => 'ij',
'/ĵ/' => 'j',
'/ķ/' => 'k',
'/ĺ|ļ|ľ|ŀ|ł/' => 'l',
'/ñ|ń|ņ|ň|ʼn/' => 'n',
'/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º/' => 'o',
'/ö|œ/' => 'oe',
'/ŕ|ŗ|ř/' => 'r',
'/ś|ŝ|ş|ș|š|ſ/' => 's',
'/ß/' => 'ss',
'/ţ|ț|ť|ŧ/' => 't',
'/þ/' => 'th',
'/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ/' => 'u',
'/ü/' => 'ue',
'/ŵ/' => 'w',
'/ý|ÿ|ŷ/' => 'y',
'/є/' => 'ye',
'/ї/' => 'yi',
'/ź|ż|ž/' => 'z',
);
/**
@ -249,13 +274,13 @@ class Inflector {
$key = '_' . $key;
$type = '_' . $type;
if ($value !== false) {
self::$_cache[$type][$key] = $value;
static::$_cache[$type][$key] = $value;
return $value;
}
if (!isset(self::$_cache[$type][$key])) {
if (!isset(static::$_cache[$type][$key])) {
return false;
}
return self::$_cache[$type][$key];
return static::$_cache[$type][$key];
}
/**
@ -265,13 +290,13 @@ class Inflector {
* @return void
*/
public static function reset() {
if (empty(self::$_initialState)) {
self::$_initialState = get_class_vars('Inflector');
if (empty(static::$_initialState)) {
static::$_initialState = get_class_vars('Inflector');
return;
}
foreach (self::$_initialState as $key => $val) {
if ($key != '_initialState') {
self::${$key} = $val;
foreach (static::$_initialState as $key => $val) {
if ($key !== '_initialState') {
static::${$key} = $val;
}
}
}
@ -281,7 +306,7 @@ class Inflector {
*
* ### Usage:
*
* {{{
* ```
* Inflector::rules('plural', array('/^(inflect)or$/i' => '\1ables'));
* Inflector::rules('plural', array(
* 'rules' => array('/^(inflect)ors$/i' => '\1ables'),
@ -289,11 +314,11 @@ class Inflector {
* 'irregular' => array('red' => 'redlings')
* ));
* Inflector::rules('transliteration', array('/å/' => 'aa'));
* }}}
* ```
*
* @param string $type The type of inflection, either 'plural', 'singular' or 'transliteration'
* @param array $rules Array of rules to be added.
* @param boolean $reset If true, will unset default inflections for all
* @param bool $reset If true, will unset default inflections for all
* new rules that are being defined in $rules.
* @return void
*/
@ -303,37 +328,36 @@ class Inflector {
switch ($type) {
case 'transliteration':
if ($reset) {
self::$_transliteration = $rules;
static::$_transliteration = $rules;
} else {
self::$_transliteration = $rules + self::$_transliteration;
static::$_transliteration = $rules + static::$_transliteration;
}
break;
break;
default:
foreach ($rules as $rule => $pattern) {
if (is_array($pattern)) {
if ($reset) {
self::${$var}[$rule] = $pattern;
static::${$var}[$rule] = $pattern;
} else {
if ($rule === 'uninflected') {
self::${$var}[$rule] = array_merge($pattern, self::${$var}[$rule]);
static::${$var}[$rule] = array_merge($pattern, static::${$var}[$rule]);
} else {
self::${$var}[$rule] = $pattern + self::${$var}[$rule];
static::${$var}[$rule] = $pattern + static::${$var}[$rule];
}
}
unset($rules[$rule], self::${$var}['cache' . ucfirst($rule)]);
if (isset(self::${$var}['merged'][$rule])) {
unset(self::${$var}['merged'][$rule]);
unset($rules[$rule], static::${$var}['cache' . ucfirst($rule)]);
if (isset(static::${$var}['merged'][$rule])) {
unset(static::${$var}['merged'][$rule]);
}
if ($type === 'plural') {
self::$_cache['pluralize'] = self::$_cache['tableize'] = array();
static::$_cache['pluralize'] = static::$_cache['tableize'] = array();
} elseif ($type === 'singular') {
self::$_cache['singularize'] = array();
static::$_cache['singularize'] = array();
}
}
}
self::${$var}['rules'] = $rules + self::${$var}['rules'];
break;
static::${$var}['rules'] = $rules + static::${$var}['rules'];
}
}
@ -345,37 +369,39 @@ class Inflector {
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/inflector.html#Inflector::pluralize
*/
public static function pluralize($word) {
if (isset(self::$_cache['pluralize'][$word])) {
return self::$_cache['pluralize'][$word];
if (isset(static::$_cache['pluralize'][$word])) {
return static::$_cache['pluralize'][$word];
}
if (!isset(self::$_plural['merged']['irregular'])) {
self::$_plural['merged']['irregular'] = self::$_plural['irregular'];
if (!isset(static::$_plural['merged']['irregular'])) {
static::$_plural['merged']['irregular'] = static::$_plural['irregular'];
}
if (!isset(self::$_plural['merged']['uninflected'])) {
self::$_plural['merged']['uninflected'] = array_merge(self::$_plural['uninflected'], self::$_uninflected);
if (!isset(static::$_plural['merged']['uninflected'])) {
static::$_plural['merged']['uninflected'] = array_merge(static::$_plural['uninflected'], static::$_uninflected);
}
if (!isset(self::$_plural['cacheUninflected']) || !isset(self::$_plural['cacheIrregular'])) {
self::$_plural['cacheUninflected'] = '(?:' . implode('|', self::$_plural['merged']['uninflected']) . ')';
self::$_plural['cacheIrregular'] = '(?:' . implode('|', array_keys(self::$_plural['merged']['irregular'])) . ')';
if (!isset(static::$_plural['cacheUninflected']) || !isset(static::$_plural['cacheIrregular'])) {
static::$_plural['cacheUninflected'] = '(?:' . implode('|', static::$_plural['merged']['uninflected']) . ')';
static::$_plural['cacheIrregular'] = '(?:' . implode('|', array_keys(static::$_plural['merged']['irregular'])) . ')';
}
if (preg_match('/(.*)\\b(' . self::$_plural['cacheIrregular'] . ')$/i', $word, $regs)) {
self::$_cache['pluralize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$_plural['merged']['irregular'][strtolower($regs[2])], 1);
return self::$_cache['pluralize'][$word];
if (preg_match('/(.*?(?:\\b|_))(' . static::$_plural['cacheIrregular'] . ')$/i', $word, $regs)) {
static::$_cache['pluralize'][$word] = $regs[1] .
substr($regs[2], 0, 1) .
substr(static::$_plural['merged']['irregular'][strtolower($regs[2])], 1);
return static::$_cache['pluralize'][$word];
}
if (preg_match('/^(' . self::$_plural['cacheUninflected'] . ')$/i', $word, $regs)) {
self::$_cache['pluralize'][$word] = $word;
if (preg_match('/^(' . static::$_plural['cacheUninflected'] . ')$/i', $word, $regs)) {
static::$_cache['pluralize'][$word] = $word;
return $word;
}
foreach (self::$_plural['rules'] as $rule => $replacement) {
foreach (static::$_plural['rules'] as $rule => $replacement) {
if (preg_match($rule, $word)) {
self::$_cache['pluralize'][$word] = preg_replace($rule, $replacement, $word);
return self::$_cache['pluralize'][$word];
static::$_cache['pluralize'][$word] = preg_replace($rule, $replacement, $word);
return static::$_cache['pluralize'][$word];
}
}
}
@ -388,46 +414,48 @@ class Inflector {
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/inflector.html#Inflector::singularize
*/
public static function singularize($word) {
if (isset(self::$_cache['singularize'][$word])) {
return self::$_cache['singularize'][$word];
if (isset(static::$_cache['singularize'][$word])) {
return static::$_cache['singularize'][$word];
}
if (!isset(self::$_singular['merged']['uninflected'])) {
self::$_singular['merged']['uninflected'] = array_merge(
self::$_singular['uninflected'],
self::$_uninflected
if (!isset(static::$_singular['merged']['uninflected'])) {
static::$_singular['merged']['uninflected'] = array_merge(
static::$_singular['uninflected'],
static::$_uninflected
);
}
if (!isset(self::$_singular['merged']['irregular'])) {
self::$_singular['merged']['irregular'] = array_merge(
self::$_singular['irregular'],
array_flip(self::$_plural['irregular'])
if (!isset(static::$_singular['merged']['irregular'])) {
static::$_singular['merged']['irregular'] = array_merge(
static::$_singular['irregular'],
array_flip(static::$_plural['irregular'])
);
}
if (!isset(self::$_singular['cacheUninflected']) || !isset(self::$_singular['cacheIrregular'])) {
self::$_singular['cacheUninflected'] = '(?:' . join('|', self::$_singular['merged']['uninflected']) . ')';
self::$_singular['cacheIrregular'] = '(?:' . join('|', array_keys(self::$_singular['merged']['irregular'])) . ')';
if (!isset(static::$_singular['cacheUninflected']) || !isset(static::$_singular['cacheIrregular'])) {
static::$_singular['cacheUninflected'] = '(?:' . implode('|', static::$_singular['merged']['uninflected']) . ')';
static::$_singular['cacheIrregular'] = '(?:' . implode('|', array_keys(static::$_singular['merged']['irregular'])) . ')';
}
if (preg_match('/(.*)\\b(' . self::$_singular['cacheIrregular'] . ')$/i', $word, $regs)) {
self::$_cache['singularize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$_singular['merged']['irregular'][strtolower($regs[2])], 1);
return self::$_cache['singularize'][$word];
if (preg_match('/(.*?(?:\\b|_))(' . static::$_singular['cacheIrregular'] . ')$/i', $word, $regs)) {
static::$_cache['singularize'][$word] = $regs[1] .
substr($regs[2], 0, 1) .
substr(static::$_singular['merged']['irregular'][strtolower($regs[2])], 1);
return static::$_cache['singularize'][$word];
}
if (preg_match('/^(' . self::$_singular['cacheUninflected'] . ')$/i', $word, $regs)) {
self::$_cache['singularize'][$word] = $word;
if (preg_match('/^(' . static::$_singular['cacheUninflected'] . ')$/i', $word, $regs)) {
static::$_cache['singularize'][$word] = $word;
return $word;
}
foreach (self::$_singular['rules'] as $rule => $replacement) {
foreach (static::$_singular['rules'] as $rule => $replacement) {
if (preg_match($rule, $word)) {
self::$_cache['singularize'][$word] = preg_replace($rule, $replacement, $word);
return self::$_cache['singularize'][$word];
static::$_cache['singularize'][$word] = preg_replace($rule, $replacement, $word);
return static::$_cache['singularize'][$word];
}
}
self::$_cache['singularize'][$word] = $word;
static::$_cache['singularize'][$word] = $word;
return $word;
}
@ -439,9 +467,9 @@ class Inflector {
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/inflector.html#Inflector::camelize
*/
public static function camelize($lowerCaseAndUnderscoredWord) {
if (!($result = self::_cache(__FUNCTION__, $lowerCaseAndUnderscoredWord))) {
if (!($result = static::_cache(__FUNCTION__, $lowerCaseAndUnderscoredWord))) {
$result = str_replace(' ', '', Inflector::humanize($lowerCaseAndUnderscoredWord));
self::_cache(__FUNCTION__, $lowerCaseAndUnderscoredWord, $result);
static::_cache(__FUNCTION__, $lowerCaseAndUnderscoredWord, $result);
}
return $result;
}
@ -454,9 +482,10 @@ class Inflector {
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/inflector.html#Inflector::underscore
*/
public static function underscore($camelCasedWord) {
if (!($result = self::_cache(__FUNCTION__, $camelCasedWord))) {
$result = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $camelCasedWord));
self::_cache(__FUNCTION__, $camelCasedWord, $result);
if (!($result = static::_cache(__FUNCTION__, $camelCasedWord))) {
$underscoredWord = preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $camelCasedWord);
$result = mb_strtolower($underscoredWord);
static::_cache(__FUNCTION__, $camelCasedWord, $result);
}
return $result;
}
@ -470,9 +499,13 @@ class Inflector {
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/inflector.html#Inflector::humanize
*/
public static function humanize($lowerCaseAndUnderscoredWord) {
if (!($result = self::_cache(__FUNCTION__, $lowerCaseAndUnderscoredWord))) {
$result = ucwords(str_replace('_', ' ', $lowerCaseAndUnderscoredWord));
self::_cache(__FUNCTION__, $lowerCaseAndUnderscoredWord, $result);
if (!($result = static::_cache(__FUNCTION__, $lowerCaseAndUnderscoredWord))) {
$result = explode(' ', str_replace('_', ' ', $lowerCaseAndUnderscoredWord));
foreach ($result as &$word) {
$word = mb_strtoupper(mb_substr($word, 0, 1)) . mb_substr($word, 1);
}
$result = implode(' ', $result);
static::_cache(__FUNCTION__, $lowerCaseAndUnderscoredWord, $result);
}
return $result;
}
@ -485,9 +518,9 @@ class Inflector {
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/inflector.html#Inflector::tableize
*/
public static function tableize($className) {
if (!($result = self::_cache(__FUNCTION__, $className))) {
if (!($result = static::_cache(__FUNCTION__, $className))) {
$result = Inflector::pluralize(Inflector::underscore($className));
self::_cache(__FUNCTION__, $className, $result);
static::_cache(__FUNCTION__, $className, $result);
}
return $result;
}
@ -500,9 +533,9 @@ class Inflector {
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/inflector.html#Inflector::classify
*/
public static function classify($tableName) {
if (!($result = self::_cache(__FUNCTION__, $tableName))) {
if (!($result = static::_cache(__FUNCTION__, $tableName))) {
$result = Inflector::camelize(Inflector::singularize($tableName));
self::_cache(__FUNCTION__, $tableName, $result);
static::_cache(__FUNCTION__, $tableName, $result);
}
return $result;
}
@ -510,16 +543,16 @@ class Inflector {
/**
* Returns camelBacked version of an underscored string.
*
* @param string $string
* @param string $string String to convert.
* @return string in variable form
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/inflector.html#Inflector::variable
*/
public static function variable($string) {
if (!($result = self::_cache(__FUNCTION__, $string))) {
if (!($result = static::_cache(__FUNCTION__, $string))) {
$camelized = Inflector::camelize(Inflector::underscore($string));
$replace = strtolower(substr($camelized, 0, 1));
$result = preg_replace('/\\w/', $replace, $camelized, 1);
self::_cache(__FUNCTION__, $string, $result);
static::_cache(__FUNCTION__, $string, $result);
}
return $result;
}
@ -537,12 +570,12 @@ class Inflector {
$quotedReplacement = preg_quote($replacement, '/');
$merge = array(
'/[^\s\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]/mu' => ' ',
'/\\s+/' => $replacement,
'/[^\s\p{Zs}\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]/mu' => ' ',
'/[\s\p{Zs}]+/mu' => $replacement,
sprintf('/^[%s]+|[%s]+$/', $quotedReplacement, $quotedReplacement) => '',
);
$map = self::$_transliteration + $merge;
$map = static::$_transliteration + $merge;
return preg_replace(array_keys($map), array_values($map), $string);
}

View file

@ -1,24 +1,25 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* Deals with Collections of objects. Keeping registries of those objects,
* Deals with Collections of objects. Keeping registries of those objects,
* loading and constructing new objects and triggering callbacks. Each subclass needs
* to implement its own load() functionality.
*
* All core subclasses of ObjectCollection by convention loaded objects are stored
* in `$this->_loaded`. Enabled objects are stored in `$this->_enabled`. In addition
* the all support an `enabled` option that controls the enabled/disabled state of the object
* in `$this->_loaded`. Enabled objects are stored in `$this->_enabled`. In addition,
* they all support an `enabled` option that controls the enabled/disabled state of the object
* when loaded.
*
* @package Cake.Utility
@ -55,13 +56,13 @@ abstract class ObjectCollection {
*
* @param string $name Name of object to load.
* @param array $options Array of configuration options for the object to be constructed.
* @return object the constructed object
* @return CakeObject the constructed object
*/
abstract public function load($name, $options = array());
/**
* Trigger a callback method on every object in the collection.
* Used to trigger methods on objects in the collection. Will fire the methods in the
* Used to trigger methods on objects in the collection. Will fire the methods in the
* order they were attached.
*
* ### Options
@ -70,7 +71,7 @@ abstract class ObjectCollection {
* Can either be a scalar value, or an array of values to break on. Defaults to `false`.
*
* - `break` Set to true to enabled breaking. When a trigger is broken, the last returned value
* will be returned. If used in combination with `collectReturn` the collected results will be returned.
* will be returned. If used in combination with `collectReturn` the collected results will be returned.
* Defaults to `false`.
*
* - `collectReturn` Set to true to collect the return of each object into an array.
@ -81,8 +82,7 @@ abstract class ObjectCollection {
* Any non-null value will modify the parameter index indicated.
* Defaults to false.
*
*
* @param string $callback|CakeEvent Method to fire on all the objects. Its assumed all the objects implement
* @param string|CakeEvent $callback Method to fire on all the objects. Its assumed all the objects implement
* the method you are calling. If an instance of CakeEvent is provided, then then Event name will parsed to
* get the callback name. This is done by getting the last word after any dot in the event name
* (eg. `Model.afterSave` event will trigger the `afterSave` callback)
@ -112,27 +112,24 @@ abstract class ObjectCollection {
$parts = explode('.', $event->name());
$callback = array_pop($parts);
}
$options = array_merge(
array(
'break' => false,
'breakOn' => false,
'collectReturn' => false,
'modParams' => false
),
$options
$options += array(
'break' => false,
'breakOn' => false,
'collectReturn' => false,
'modParams' => false
);
$collected = array();
$list = array_keys($this->_enabled);
if ($options['modParams'] !== false && !isset($params[$options['modParams']])) {
throw new CakeException(__d('cake_dev', 'Cannot use modParams with indexes that do not exist.'));
}
$result = null;
foreach ($list as $name) {
$result = call_user_func_array(array($this->_loaded[$name], $callback), compact('subject') + $params);
if ($options['collectReturn'] === true) {
$collected[] = $result;
}
if (
$options['break'] && ($result === $options['breakOn'] ||
if ($options['break'] && ($result === $options['breakOn'] ||
(is_array($options['breakOn']) && in_array($result, $options['breakOn'], true)))
) {
return $result;
@ -163,7 +160,7 @@ abstract class ObjectCollection {
* Provide isset access to _loaded
*
* @param string $name Name of object being checked.
* @return boolean
* @return bool
*/
public function __isset($name) {
return isset($this->_loaded[$name]);
@ -173,14 +170,18 @@ abstract class ObjectCollection {
* Enables callbacks on an object or array of objects
*
* @param string|array $name CamelCased name of the object(s) to enable (string or array)
* @param boolean Prioritize enabled list after enabling object(s)
* @param bool $prioritize Prioritize enabled list after enabling object(s)
* @return void
*/
public function enable($name, $prioritize = true) {
$enabled = false;
foreach ((array)$name as $object) {
list(, $object) = pluginSplit($object);
if (isset($this->_loaded[$object]) && !isset($this->_enabled[$object])) {
$priority = isset($this->_loaded[$object]->settings['priority']) ? $this->_loaded[$object]->settings['priority'] : $this->defaultPriority;
$priority = $this->defaultPriority;
if (isset($this->_loaded[$object]->settings['priority'])) {
$priority = $this->_loaded[$object]->settings['priority'];
}
$this->_enabled[$object] = array($priority);
$enabled = true;
}
@ -211,21 +212,22 @@ abstract class ObjectCollection {
* @param string|array $name CamelCased name of the object(s) to enable (string or array)
* If string the second param $priority is used else it should be an associative array
* with keys as object names and values as priorities to set.
* @param integer|null Integer priority to set or null for default
* @param int|null $priority Integer priority to set or null for default
* @return void
*/
public function setPriority($name, $priority = null) {
if (is_string($name)) {
$name = array($name => $priority);
}
foreach ($name as $obj => $prio) {
if (isset($this->_loaded[$obj])) {
if (is_null($prio)) {
$prio = $this->defaultPriority;
foreach ($name as $object => $objectPriority) {
list(, $object) = pluginSplit($object);
if (isset($this->_loaded[$object])) {
if ($objectPriority === null) {
$objectPriority = $this->defaultPriority;
}
$this->_loaded[$obj]->settings['priority'] = $prio;
if (isset($this->_enabled[$obj])) {
$this->_enabled[$obj] = array($prio);
$this->_loaded[$object]->settings['priority'] = $objectPriority;
if (isset($this->_enabled[$object])) {
$this->_enabled[$object] = array($objectPriority);
}
}
}
@ -233,7 +235,7 @@ abstract class ObjectCollection {
}
/**
* Disables callbacks on a object or array of objects. Public object methods are still
* Disables callbacks on an object or array of objects. Public object methods are still
* callable as normal.
*
* @param string|array $name CamelCased name of the objects(s) to disable (string or array)
@ -241,6 +243,7 @@ abstract class ObjectCollection {
*/
public function disable($name) {
foreach ((array)$name as $object) {
list(, $object) = pluginSplit($object);
unset($this->_enabled[$object]);
}
}
@ -248,13 +251,14 @@ abstract class ObjectCollection {
/**
* Gets the list of currently-enabled objects, or, the current status of a single objects
*
* @param string $name Optional. The name of the object to check the status of. If omitted,
* @param string $name Optional. The name of the object to check the status of. If omitted,
* returns an array of currently-enabled object
* @return mixed If $name is specified, returns the boolean status of the corresponding object.
* Otherwise, returns an array of all enabled objects.
*/
public function enabled($name = null) {
if (!empty($name)) {
list(, $name) = pluginSplit($name);
return isset($this->_enabled[$name]);
}
return array_keys($this->_enabled);
@ -263,13 +267,27 @@ abstract class ObjectCollection {
/**
* Gets the list of attached objects, or, whether the given object is attached
*
* @param string $name Optional. The name of the behavior to check the status of. If omitted,
* returns an array of currently-attached behaviors
* @return mixed If $name is specified, returns the boolean status of the corresponding behavior.
* Otherwise, returns an array of all attached behaviors.
* @param string $name Optional. The name of the object to check the status of. If omitted,
* returns an array of currently-attached objects
* @return mixed If $name is specified, returns the boolean status of the corresponding object.
* Otherwise, returns an array of all attached objects.
* @deprecated 3.0.0 Will be removed in 3.0. Use loaded instead.
*/
public function attached($name = null) {
return $this->loaded($name);
}
/**
* Gets the list of loaded objects, or, whether the given object is loaded
*
* @param string $name Optional. The name of the object to check the status of. If omitted,
* returns an array of currently-loaded objects
* @return mixed If $name is specified, returns the boolean status of the corresponding object.
* Otherwise, returns an array of all loaded objects.
*/
public function loaded($name = null) {
if (!empty($name)) {
list(, $name) = pluginSplit($name);
return isset($this->_loaded[$name]);
}
return array_keys($this->_loaded);
@ -282,21 +300,20 @@ abstract class ObjectCollection {
* @return void
*/
public function unload($name) {
list($plugin, $name) = pluginSplit($name);
unset($this->_loaded[$name]);
unset($this->_enabled[$name]);
list(, $name) = pluginSplit($name);
unset($this->_loaded[$name], $this->_enabled[$name]);
}
/**
* Adds or overwrites an instantiated object to the collection
*
* @param string $name Name of the object
* @param Object $object The object to use
* @param CakeObject $object The object to use
* @return array Loaded objects
*/
public function set($name = null, $object = null) {
if (!empty($name) && !empty($object)) {
list($plugin, $name) = pluginSplit($name);
list(, $name) = pluginSplit($name);
$this->_loaded[$name] = $object;
}
return $this->_loaded;
@ -317,7 +334,7 @@ abstract class ObjectCollection {
$options = (array)$objectName;
$objectName = $i;
}
list($plugin, $name) = pluginSplit($objectName);
list(, $name) = pluginSplit($objectName);
$normal[$name] = array('class' => $objectName, 'settings' => $options);
}
return $normal;

View file

@ -4,22 +4,21 @@
*
* Helpful methods to make unsafe strings usable.
*
* PHP 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Utility
* @since CakePHP(tm) v 0.10.0.1076
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
App::import('Model', 'ConnectionManager');
App::uses('ConnectionManager', 'Model');
/**
* Data Sanitization.
@ -28,6 +27,7 @@ App::import('Model', 'ConnectionManager');
* and all of the above on arrays.
*
* @package Cake.Utility
* @deprecated 3.0.0 Deprecated since version 2.4
*/
class Sanitize {
@ -46,14 +46,15 @@ class Sanitize {
}
}
if (is_array($string)) {
$cleaned = array();
foreach ($string as $key => $clean) {
$cleaned[$key] = preg_replace("/[^{$allow}a-zA-Z0-9]/", '', $clean);
}
} else {
$cleaned = preg_replace("/[^{$allow}a-zA-Z0-9]/", '', $string);
if (!is_array($string)) {
return preg_replace("/[^{$allow}a-zA-Z0-9]/", '', $string);
}
$cleaned = array();
foreach ($string as $key => $clean) {
$cleaned[$key] = preg_replace("/[^{$allow}a-zA-Z0-9]/", '', $clean);
}
return $cleaned;
}
@ -65,19 +66,17 @@ class Sanitize {
* @return string SQL safe string
*/
public static function escape($string, $connection = 'default') {
$db = ConnectionManager::getDataSource($connection);
if (is_numeric($string) || $string === null || is_bool($string)) {
return $string;
}
$db = ConnectionManager::getDataSource($connection);
$string = $db->value($string, 'string');
if ($string[0] === 'N') {
$string = substr($string, 2);
} else {
$string = substr($string, 1);
$start = 1;
if ($string{0} === 'N') {
$start = 2;
}
$string = substr($string, 0, -1);
return $string;
return substr(substr($string, $start), 0, -1);
}
/**
@ -91,7 +90,7 @@ class Sanitize {
* - remove (boolean) if true strips all HTML tags before encoding
* - charset (string) the charset used to encode the string
* - quotes (int) see http://php.net/manual/en/function.htmlentities.php
* - double (boolean) doube encode html entities
* - double (boolean) double encode html entities
*
* @param string $string String from where to strip tags
* @param array $options Array of options to use.
@ -105,14 +104,14 @@ class Sanitize {
$defaultCharset = 'UTF-8';
}
}
$default = array(
$defaults = array(
'remove' => false,
'charset' => $defaultCharset,
'quotes' => ENT_QUOTES,
'double' => true
);
$options = array_merge($default, $options);
$options += $defaults;
if ($options['remove']) {
$string = strip_tags($string);
@ -128,8 +127,7 @@ class Sanitize {
* @return string whitespace sanitized string
*/
public static function stripWhitespace($str) {
$r = preg_replace('/[\n\r\t]+/', '', $str);
return preg_replace('/\s{2,}/u', ' ', $r);
return preg_replace('/\s{2,}/u', ' ', preg_replace('/[\n\r\t]+/', '', $str));
}
/**
@ -139,20 +137,29 @@ class Sanitize {
* @return string Sting with images stripped.
*/
public static function stripImages($str) {
$str = preg_replace('/(<a[^>]*>)(<img[^>]+alt=")([^"]*)("[^>]*>)(<\/a>)/i', '$1$3$5<br />', $str);
$str = preg_replace('/(<img[^>]+alt=")([^"]*)("[^>]*>)/i', '$2<br />', $str);
$str = preg_replace('/<img[^>]*>/i', '', $str);
return $str;
$preg = array(
'/(<a[^>]*>)(<img[^>]+alt=")([^"]*)("[^>]*>)(<\/a>)/i' => '$1$3$5<br />',
'/(<img[^>]+alt=")([^"]*)("[^>]*>)/i' => '$2<br />',
'/<img[^>]*>/i' => ''
);
return preg_replace(array_keys($preg), array_values($preg), $str);
}
/**
* Strips scripts and stylesheets from output
*
* @param string $str String to sanitize
* @return string String with <script>, <style>, <link>, <img> elements removed.
* @return string String with <link>, <img>, <script>, <style> elements and html comments removed.
*/
public static function stripScripts($str) {
return preg_replace('/(<link[^>]+rel="[^"]*stylesheet"[^>]*>|<img[^>]*>|style="[^"]*")|<script[^>]*>.*?<\/script>|<style[^>]*>.*?<\/style>|<!--.*?-->/is', '', $str);
$regex =
'/(<link[^>]+rel="[^"]*stylesheet"[^>]*>|' .
'<img[^>]*>|style="[^"]*")|' .
'<script[^>]*>.*?<\/script>|' .
'<style[^>]*>.*?<\/style>|' .
'<!--.*?-->/is';
return preg_replace($regex, '', $str);
}
/**
@ -162,10 +169,11 @@ class Sanitize {
* @return string sanitized string
*/
public static function stripAll($str) {
$str = Sanitize::stripWhitespace($str);
$str = Sanitize::stripImages($str);
$str = Sanitize::stripScripts($str);
return $str;
return Sanitize::stripScripts(
Sanitize::stripImages(
Sanitize::stripWhitespace($str)
)
);
}
/**
@ -176,7 +184,7 @@ class Sanitize {
*
* Will remove all `<b>`, `<p>`, and `<div>` tags from the $dirty string.
*
* @param string $str,... String to sanitize
* @param string $str String to sanitize.
* @return string sanitized String
*/
public static function stripTags($str) {
@ -212,13 +220,11 @@ class Sanitize {
return $data;
}
if (is_string($options)) {
if (!is_array($options)) {
$options = array('connection' => $options);
} elseif (!is_array($options)) {
$options = array();
}
$options = array_merge(array(
$options += array(
'connection' => 'default',
'odd_spaces' => true,
'remove_html' => false,
@ -228,37 +234,36 @@ class Sanitize {
'unicode' => true,
'escape' => true,
'backslash' => true
), $options);
);
if (is_array($data)) {
foreach ($data as $key => $val) {
$data[$key] = Sanitize::clean($val, $options);
}
return $data;
} else {
if ($options['odd_spaces']) {
$data = str_replace(chr(0xCA), '', $data);
}
if ($options['encode']) {
$data = Sanitize::html($data, array('remove' => $options['remove_html']));
}
if ($options['dollar']) {
$data = str_replace("\\\$", "$", $data);
}
if ($options['carriage']) {
$data = str_replace("\r", "", $data);
}
if ($options['unicode']) {
$data = preg_replace("/&amp;#([0-9]+);/s", "&#\\1;", $data);
}
if ($options['escape']) {
$data = Sanitize::escape($data, $options['connection']);
}
if ($options['backslash']) {
$data = preg_replace("/\\\(?!&amp;#|\?#)/", "\\", $data);
}
return $data;
}
}
if ($options['odd_spaces']) {
$data = str_replace(chr(0xCA), '', $data);
}
if ($options['encode']) {
$data = Sanitize::html($data, array('remove' => $options['remove_html']));
}
if ($options['dollar']) {
$data = str_replace("\\\$", "$", $data);
}
if ($options['carriage']) {
$data = str_replace("\r", "", $data);
}
if ($options['unicode']) {
$data = preg_replace("/&amp;#([0-9]+);/s", "&#\\1;", $data);
}
if ($options['escape']) {
$data = Sanitize::escape($data, $options['connection']);
}
if ($options['backslash']) {
$data = preg_replace("/\\\(?!&amp;#|\?#)/", "\\", $data);
}
return $data;
}
}

View file

@ -2,22 +2,21 @@
/**
* Core Security
*
* PHP 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Utility
* @since CakePHP(tm) v .0.10.0.1233
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
App::uses('String', 'Utility');
App::uses('CakeText', 'Utility');
/**
* Security Library contains utility methods related to security
@ -33,10 +32,18 @@ class Security {
*/
public static $hashType = null;
/**
* Default cost
*
* @var string
*/
public static $hashCost = '10';
/**
* Get allowed minutes of inactivity based on security level.
*
* @return integer Allowed inactivity in minutes
* @deprecated 3.0.0 Exists for backwards compatibility only, not used by the core
* @return int Allowed inactivity in minutes
*/
public static function inactiveMins() {
switch (Configure::read('Security.level')) {
@ -54,54 +61,74 @@ class Security {
* Generate authorization hash.
*
* @return string Hash
* @deprecated 2.8.1 This method was removed in 3.0.0
*/
public static function generateAuthKey() {
return Security::hash(String::uuid());
return Security::hash(CakeText::uuid());
}
/**
* Validate authorization hash.
*
* @param string $authKey Authorization hash
* @return boolean Success
* @return bool Success
* @deprecated 2.8.1 This method was removed in 3.0.0
*/
public static function validateAuthKey($authKey) {
return true;
}
/**
* Create a hash from string using given method.
* Fallback on next available method.
* Create a hash from string using given method or fallback on next available method.
*
* #### Using Blowfish
*
* - Creating Hashes: *Do not supply a salt*. Cake handles salt creation for
* you ensuring that each hashed password will have a *unique* salt.
* - Comparing Hashes: Simply pass the originally hashed password as the salt.
* The salt is prepended to the hash and php handles the parsing automagically.
* For convenience the `BlowfishPasswordHasher` class is available for use with
* the AuthComponent.
* - Do NOT use a constant salt for blowfish!
*
* Creating a blowfish/bcrypt hash:
*
* ```
* $hash = Security::hash($password, 'blowfish');
* ```
*
* @param string $string String to hash
* @param string $type Method to use (sha1/sha256/md5)
* @param boolean $salt If true, automatically appends the application's salt
* value to $string (Security.salt)
* @param string $type Method to use (sha1/sha256/md5/blowfish)
* @param mixed $salt If true, automatically prepends the application's salt
* value to $string (Security.salt). If you are using blowfish the salt
* must be false or a previously generated salt.
* @return string Hash
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/security.html#Security::hash
*/
public static function hash($string, $type = null, $salt = false) {
if ($salt) {
if (is_string($salt)) {
$string = $salt . $string;
} else {
$string = Configure::read('Security.salt') . $string;
}
}
if (empty($type)) {
$type = self::$hashType;
$type = static::$hashType;
}
$type = strtolower($type);
if ($type == 'sha1' || $type == null) {
if ($type === 'blowfish') {
return static::_crypt($string, $salt);
}
if ($salt) {
if (!is_string($salt)) {
$salt = Configure::read('Security.salt');
}
$string = $salt . $string;
}
if (!$type || $type === 'sha1') {
if (function_exists('sha1')) {
$return = sha1($string);
return $return;
return sha1($string);
}
$type = 'sha256';
}
if ($type == 'sha256' && function_exists('mhash')) {
if ($type === 'sha256' && function_exists('mhash')) {
return bin2hex(mhash(MHASH_SHA256, $string));
}
@ -112,31 +139,88 @@ class Security {
}
/**
* Sets the default hash method for the Security object. This affects all objects using
* Sets the default hash method for the Security object. This affects all objects using
* Security::hash().
*
* @param string $hash Method to use (sha1/sha256/md5)
* @param string $hash Method to use (sha1/sha256/md5/blowfish)
* @return void
* @see Security::hash()
*/
public static function setHash($hash) {
self::$hashType = $hash;
static::$hashType = $hash;
}
/**
* Encrypts/Decrypts a text using the given key.
* Sets the cost for they blowfish hash method.
*
* @param int $cost Valid values are 4-31
* @return void
*/
public static function setCost($cost) {
if ($cost < 4 || $cost > 31) {
trigger_error(__d(
'cake_dev',
'Invalid value, cost must be between %s and %s',
array(4, 31)
), E_USER_WARNING);
return null;
}
static::$hashCost = $cost;
}
/**
* Get random bytes from a secure source.
*
* This method will fall back to an insecure source and trigger a warning,
* if it cannot find a secure source of random data.
*
* @param int $length The number of bytes you want.
* @return string Random bytes in binary.
*/
public static function randomBytes($length) {
if (function_exists('random_bytes')) {
return random_bytes($length);
}
if (function_exists('openssl_random_pseudo_bytes')) {
return openssl_random_pseudo_bytes($length);
}
trigger_error(
'You do not have a safe source of random data available. ' .
'Install either the openssl extension, or paragonie/random_compat. ' .
'Falling back to an insecure random source.',
E_USER_WARNING
);
$bytes = '';
$byteLength = 0;
while ($byteLength < $length) {
$bytes .= static::hash(CakeText::uuid() . uniqid(mt_rand(), true), 'sha512', true);
$byteLength = strlen($bytes);
}
return substr($bytes, 0, $length);
}
/**
* Runs $text through a XOR cipher.
*
* *Note* This is not a cryptographically strong method and should not be used
* for sensitive data. Additionally this method does *not* work in environments
* where suhosin is enabled.
*
* Instead you should use Security::rijndael() when you need strong
* encryption.
*
* @param string $text Encrypted string to decrypt, normal string to encrypt
* @param string $key Key to use
* @return string Encrypted/Decrypted string
* @deprecated 3.0.0 Will be removed in 3.0.
*/
public static function cipher($text, $key) {
if (empty($key)) {
trigger_error(__d('cake_dev', 'You cannot use an empty key for Security::cipher()'), E_USER_WARNING);
trigger_error(__d('cake_dev', 'You cannot use an empty key for %s', 'Security::cipher()'), E_USER_WARNING);
return '';
}
srand(Configure::read('Security.cipherSeed'));
srand((int)Configure::read('Security.cipherSeed'));
$out = '';
$keyLength = strlen($key);
for ($i = 0, $textLength = strlen($text); $i < $textLength; $i++) {
@ -154,14 +238,18 @@ class Security {
/**
* Encrypts/Decrypts a text using the given key using rijndael method.
*
* Prior to 2.3.1, a fixed initialization vector was used. This was not
* secure. This method now uses a random iv, and will silently upgrade values when
* they are re-encrypted.
*
* @param string $text Encrypted string to decrypt, normal string to encrypt
* @param string $key Key to use
* @param string $key Key to use as the encryption key for encrypted data.
* @param string $operation Operation to perform, encrypt or decrypt
* @return string Encrypted/Descrypted string
* @return string Encrypted/Decrypted string
*/
public static function rijndael($text, $key, $operation) {
if (empty($key)) {
trigger_error(__d('cake_dev', 'You cannot use an empty key for Security::rijndael()'), E_USER_WARNING);
trigger_error(__d('cake_dev', 'You cannot use an empty key for %s', 'Security::rijndael()'), E_USER_WARNING);
return '';
}
if (empty($operation) || !in_array($operation, array('encrypt', 'decrypt'))) {
@ -172,17 +260,158 @@ class Security {
trigger_error(__d('cake_dev', 'You must use a key larger than 32 bytes for Security::rijndael()'), E_USER_WARNING);
return '';
}
$algorithm = 'rijndael-256';
$mode = 'cbc';
$algorithm = MCRYPT_RIJNDAEL_256;
$mode = MCRYPT_MODE_CBC;
$ivSize = mcrypt_get_iv_size($algorithm, $mode);
$cryptKey = substr($key, 0, 32);
$iv = substr($key, strlen($key) - 32, 32);
$out = '';
if ($operation === 'encrypt') {
$out .= mcrypt_encrypt($algorithm, $cryptKey, $text, $mode, $iv);
} elseif ($operation === 'decrypt') {
$out .= rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");
$iv = mcrypt_create_iv($ivSize, MCRYPT_RAND);
return $iv . '$$' . mcrypt_encrypt($algorithm, $cryptKey, $text, $mode, $iv);
}
return $out;
// Backwards compatible decrypt with fixed iv
if (substr($text, $ivSize, 2) !== '$$') {
$iv = substr($key, strlen($key) - 32, 32);
return rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");
}
$iv = substr($text, 0, $ivSize);
$text = substr($text, $ivSize + 2);
return rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");
}
/**
* Generates a pseudo random salt suitable for use with php's crypt() function.
* The salt length should not exceed 27. The salt will be composed of
* [./0-9A-Za-z]{$length}.
*
* @param int $length The length of the returned salt
* @return string The generated salt
*/
protected static function _salt($length = 22) {
$salt = str_replace(
array('+', '='),
'.',
base64_encode(sha1(uniqid(Configure::read('Security.salt'), true), true))
);
return substr($salt, 0, $length);
}
/**
* One way encryption using php's crypt() function. To use blowfish hashing see ``Security::hash()``
*
* @param string $password The string to be encrypted.
* @param mixed $salt false to generate a new salt or an existing salt.
* @return string The hashed string or an empty string on error.
*/
protected static function _crypt($password, $salt = false) {
if ($salt === false || $salt === null || $salt === '') {
$salt = static::_salt(22);
$salt = vsprintf('$2a$%02d$%s', array(static::$hashCost, $salt));
}
$invalidCipher = (
strpos($salt, '$2y$') !== 0 &&
strpos($salt, '$2x$') !== 0 &&
strpos($salt, '$2a$') !== 0
);
if ($salt === true || $invalidCipher || strlen($salt) < 29) {
trigger_error(__d(
'cake_dev',
'Invalid salt: %s for %s Please visit http://www.php.net/crypt and read the appropriate section for building %s salts.',
array($salt, 'blowfish', 'blowfish')
), E_USER_WARNING);
return '';
}
return crypt($password, $salt);
}
/**
* Encrypt a value using AES-256.
*
* *Caveat* You cannot properly encrypt/decrypt data with trailing null bytes.
* Any trailing null bytes will be removed on decryption due to how PHP pads messages
* with nulls prior to encryption.
*
* @param string $plain The value to encrypt.
* @param string $key The 256 bit/32 byte key to use as a cipher key.
* @param string $hmacSalt The salt to use for the HMAC process. Leave null to use Security.salt.
* @return string Encrypted data.
* @throws CakeException On invalid data or key.
*/
public static function encrypt($plain, $key, $hmacSalt = null) {
static::_checkKey($key, 'encrypt()');
if ($hmacSalt === null) {
$hmacSalt = Configure::read('Security.salt');
}
// Generate the encryption and hmac key.
$key = substr(hash('sha256', $key . $hmacSalt), 0, 32);
$algorithm = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_CBC;
$ivSize = mcrypt_get_iv_size($algorithm, $mode);
$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
$ciphertext = $iv . mcrypt_encrypt($algorithm, $key, $plain, $mode, $iv);
$hmac = hash_hmac('sha256', $ciphertext, $key);
return $hmac . $ciphertext;
}
/**
* Check the encryption key for proper length.
*
* @param string $key Key to check.
* @param string $method The method the key is being checked for.
* @return void
* @throws CakeException When key length is not 256 bit/32 bytes
*/
protected static function _checkKey($key, $method) {
if (strlen($key) < 32) {
throw new CakeException(__d('cake_dev', 'Invalid key for %s, key must be at least 256 bits (32 bytes) long.', $method));
}
}
/**
* Decrypt a value using AES-256.
*
* @param string $cipher The ciphertext to decrypt.
* @param string $key The 256 bit/32 byte key to use as a cipher key.
* @param string $hmacSalt The salt to use for the HMAC process. Leave null to use Security.salt.
* @return string Decrypted data. Any trailing null bytes will be removed.
* @throws CakeException On invalid data or key.
*/
public static function decrypt($cipher, $key, $hmacSalt = null) {
static::_checkKey($key, 'decrypt()');
if (empty($cipher)) {
throw new CakeException(__d('cake_dev', 'The data to decrypt cannot be empty.'));
}
if ($hmacSalt === null) {
$hmacSalt = Configure::read('Security.salt');
}
// Generate the encryption and hmac key.
$key = substr(hash('sha256', $key . $hmacSalt), 0, 32);
// Split out hmac for comparison
$macSize = 64;
$hmac = substr($cipher, 0, $macSize);
$cipher = substr($cipher, $macSize);
$compareHmac = hash_hmac('sha256', $cipher, $key);
if ($hmac !== $compareHmac) {
return false;
}
$algorithm = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_CBC;
$ivSize = mcrypt_get_iv_size($algorithm, $mode);
$iv = substr($cipher, 0, $ivSize);
$cipher = substr($cipher, $ivSize);
$plain = mcrypt_decrypt($algorithm, $key, $cipher, $mode, $iv);
return rtrim($plain, "\0");
}
}

View file

@ -2,28 +2,28 @@
/**
* Library of array functions for Cake.
*
* PHP 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Utility
* @since CakePHP(tm) v 1.2.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
App::uses('String', 'Utility');
App::uses('CakeText', 'Utility');
App::uses('Hash', 'Utility');
/**
* Class used for manipulation of arrays.
*
* @package Cake.Utility
* @deprecated 3.0.0 Will be removed in 3.0. Use Hash instead.
*/
class Set {
@ -32,7 +32,7 @@ class Set {
* to the two is that if an array key contains another array then the function behaves recursive (unlike array_merge)
* but does not do if for keys containing strings (unlike array_merge_recursive).
*
* Since this method emulates `array_merge`, it will re-order numeric keys. When combined with out of
* Since this method emulates `array_merge`, it will re-order numeric keys. When combined with out of
* order numeric keys containing arrays, results can be lossy.
*
* Note: This function will work with an unlimited amount of arguments and typecasts non-array
@ -97,7 +97,7 @@ class Set {
*
* @param string $class A class name of the type of object to map to
* @param string $tmp A temporary class name used as $class if $class is an array
* @return object Hierarchical object
* @return object|null Hierarchical object
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::map
*/
public static function map($class = 'stdClass', $tmp = 'stdClass') {
@ -120,9 +120,9 @@ class Set {
* returned object (recursively). If $key is numeric will maintain array
* structure
*
* @param array $array Array to map
* @param array &$array Array to map
* @param string $class Class name
* @param boolean $primary whether to assign first array key as the _name_
* @param bool $primary whether to assign first array key as the _name_
* @return mixed Mapped object
*/
protected static function _map(&$array, $class, $primary = false) {
@ -185,8 +185,8 @@ class Set {
/**
* Checks to see if all the values in the array are numeric
*
* @param array $array The array to check. If null, the value of the current Set object
* @return boolean true if values are numeric, false otherwise
* @param array $array The array to check. If null, the value of the current Set object
* @return bool true if values are numeric, false otherwise
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::numeric
*/
public static function numeric($array = null) {
@ -203,7 +203,7 @@ class Set {
*
* $list defaults to 0 = no 1 = yes if param is not passed
*
* @param array $select Key in $list to return
* @param string $select Key in $list to return
* @param array|string $list can be an array or a comma-separated list.
* @return string the value of the array key or null if no match
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::enum
@ -228,7 +228,7 @@ class Set {
* @param array $data Source array from which to extract the data
* @param string $format Format string into which values will be inserted, see sprintf()
* @param array $keys An array containing one or more Set::extract()-style key paths
* @return array An array of strings extracted from $keys and formatted with $format
* @return array|null An array of strings extracted from $keys and formatted with $format, otherwise null.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::format
*/
public static function format($data, $format, $keys) {
@ -236,7 +236,7 @@ class Set {
$count = count($keys);
if (!$count) {
return;
return null;
}
for ($i = 0; $i < $count; $i++) {
@ -308,7 +308,7 @@ class Set {
* @param string $path An absolute XPath 2.0 path
* @param array $data An array of data to extract from
* @param array $options Currently only supports 'flatten' which can be disabled for higher XPath-ness
* @return array An array of matched items
* @return mixed An array of matched items or the content of a single selected item or null in any of these cases: $path or $data are null, no items found.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::extract
*/
public static function extract($path, $data = null, $options = array()) {
@ -327,7 +327,7 @@ class Set {
return $data;
}
$contexts = $data;
$options = array_merge(array('flatten' => true), $options);
$options += array('flatten' => true);
if (!isset($contexts[0])) {
$current = current($data);
if ((is_array($current) && count($data) < 1) || !is_array($current) || !Set::numeric(array_keys($data))) {
@ -349,7 +349,7 @@ class Set {
$context = array('trace' => array(null), 'item' => $context, 'key' => $key);
}
if ($token === '..') {
if (count($context['trace']) == 1) {
if (count($context['trace']) === 1) {
$context['trace'][] = $context['key'];
}
$parent = implode('/', $context['trace']) . '/.';
@ -373,7 +373,7 @@ class Set {
);
} elseif (is_array($context['item'])
&& array_key_exists($token, $context['item'])
&& !(strval($key) === strval($token) && count($tokens) == 1 && $tokens[0] === '.')) {
&& !(strval($key) === strval($token) && count($tokens) === 1 && $tokens[0] === '.')) {
$items = $context['item'][$token];
if (!is_array($items)) {
$items = array($items);
@ -456,10 +456,10 @@ class Set {
* This function can be used to see if a single item or a given xpath match certain conditions.
*
* @param string|array $conditions An array of condition strings or an XPath expression
* @param array $data An array of data to execute the match on
* @param integer $i Optional: The 'nth'-number of the item being matched.
* @param integer $length
* @return boolean
* @param array $data An array of data to execute the match on
* @param int $i Optional: The 'nth'-number of the item being matched.
* @param int $length Length.
* @return bool
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::matches
*/
public static function matches($conditions, $data = array(), $i = null, $length = null) {
@ -467,7 +467,7 @@ class Set {
return true;
}
if (is_string($conditions)) {
return !!Set::extract($conditions, $data);
return (bool)Set::extract($conditions, $data);
}
foreach ($conditions as $condition) {
if ($condition === ':last') {
@ -533,7 +533,7 @@ class Set {
*
* @param array $data Array from where to extract
* @param string|array $path As an array, or as a dot-separated string.
* @return array|null Extracted data or null when $data or $path are empty.
* @return mixed An array of matched items or the content of a single selected item or null in any of these cases: $path or $data are null, no items found.
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::classicExtract
*/
public static function classicExtract($data, $path = null) {
@ -549,7 +549,7 @@ class Set {
return null;
}
if (is_string($path) && strpos($path, '{') !== false) {
$path = String::tokenize($path, '.', '{', '}');
$path = CakeText::tokenize($path, '.', '{', '}');
} elseif (is_string($path)) {
$path = explode('.', $path);
}
@ -560,7 +560,7 @@ class Set {
}
foreach ($path as $i => $key) {
if (is_numeric($key) && intval($key) > 0 || $key === '0') {
if (is_numeric($key) && (int)$key > 0 || $key === '0') {
if (isset($data[$key])) {
$data = $data[$key];
} else {
@ -590,7 +590,7 @@ class Set {
}
}
return $tmp;
} elseif (false !== strpos($key, '{') && false !== strpos($key, '}')) {
} elseif (strpos($key, '{') !== false && strpos($key, '}') !== false) {
$pattern = substr($key, 1, -1);
foreach ($data as $j => $val) {
@ -645,7 +645,7 @@ class Set {
*
* @param string|array $data Data to check on
* @param string|array $path A dot-separated string.
* @return boolean true if path is found, false otherwise
* @return bool true if path is found, false otherwise
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::check
*/
public static function check($data, $path = null) {
@ -657,8 +657,8 @@ class Set {
}
foreach ($path as $i => $key) {
if (is_numeric($key) && intval($key) > 0 || $key === '0') {
$key = intval($key);
if (is_numeric($key) && (int)$key > 0 || $key === '0') {
$key = (int)$key;
}
if ($i === count($path) - 1) {
return (is_array($data) && array_key_exists($key, $data));
@ -705,7 +705,7 @@ class Set {
*
* @param array $val1 First value
* @param array $val2 Second value
* @return boolean true if $val1 contains $val2, false otherwise
* @return bool true if $val1 contains $val2, false otherwise
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::contains
*/
public static function contains($val1, $val2 = null) {
@ -730,12 +730,12 @@ class Set {
* only consider the dimension of the first element in the array.
*
* @param array $array Array to count dimensions on
* @param boolean $all Set to true to count the dimension considering all elements in array
* @param integer $count Start the dimension count at this number
* @return integer The number of dimensions in $array
* @param bool $all Set to true to count the dimension considering all elements in array
* @param int $count Start the dimension count at this number
* @return int The number of dimensions in $array
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::countDim
*/
public static function countDim($array = null, $all = false, $count = 0) {
public static function countDim($array, $all = false, $count = 0) {
if ($all) {
$depth = array($count);
if (is_array($array) && reset($array) !== false) {
@ -758,9 +758,9 @@ class Set {
* Normalizes a string or array list.
*
* @param mixed $list List to normalize
* @param boolean $assoc If true, $list will be converted to an associative array
* @param bool $assoc If true, $list will be converted to an associative array
* @param string $sep If $list is a string, it will be split into an array with $sep
* @param boolean $trim If true, separated strings will be trimmed
* @param bool $trim If true, separated strings will be trimmed
* @return array
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::normalize
*/
@ -827,7 +827,7 @@ class Set {
}
}
if ($groupPath != null) {
if ($groupPath) {
$group = Set::extract($data, $groupPath);
if (!empty($group)) {
$c = count($keys);
@ -851,6 +851,7 @@ class Set {
/**
* Converts an object into an array.
*
* @param object $object Object to reverse
* @return array Array representation of given object
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::reverse
@ -909,7 +910,7 @@ class Set {
}
/**
* Expand/unflattens an string to an array
* Expand/unflattens a string to an array
*
* For example, unflattens an array that was collapsed with `Set::flatten()`
* into a multi-dimensional array. So, `array('0.Foo.Bar' => 'Far')` becomes
@ -926,15 +927,15 @@ class Set {
/**
* Flattens an array for sorting
*
* @param array $results
* @param string $key
* @param array $results Array to flatten.
* @param string $key Key.
* @return array
*/
protected static function _flatten($results, $key = null) {
$stack = array();
foreach ($results as $k => $r) {
$id = $k;
if (!is_null($key)) {
if ($key !== null) {
$id = $key;
}
if (is_array($r) && !empty($r)) {
@ -1010,7 +1011,7 @@ class Set {
*/
public static function apply($path, $data, $callback, $options = array()) {
$defaults = array('type' => 'pass');
$options = array_merge($defaults, $options);
$options += $defaults;
$extracted = Set::extract($path, $data);
if ($options['type'] === 'map') {
@ -1026,7 +1027,7 @@ class Set {
/**
* Takes in a flat array and returns a nested array
*
* @param mixed $data
* @param mixed $data Data
* @param array $options Options are:
* children - the key name to use in the resultset for children
* idPath - the path to a key that identifies each entry

View file

@ -2,604 +2,26 @@
/**
* String handling methods.
*
* PHP 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Utility
* @since CakePHP(tm) v 1.2.0.5551
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
App::uses('CakeText', 'Utility');
/**
* String handling methods.
*
*
* @package Cake.Utility
* @package Cake.Utility
* @deprecated 3.0.0 Deprecated since version 2.7, use CakeText class instead.
*/
class String {
/**
* Generate a random UUID
*
* @see http://www.ietf.org/rfc/rfc4122.txt
* @return RFC 4122 UUID
*/
public static function uuid() {
$node = env('SERVER_ADDR');
if (strpos($node, ':') !== false) {
if (substr_count($node, '::')) {
$node = str_replace(
'::', str_repeat(':0000', 8 - substr_count($node, ':')) . ':', $node
);
}
$node = explode(':', $node);
$ipSix = '';
foreach ($node as $id) {
$ipSix .= str_pad(base_convert($id, 16, 2), 16, 0, STR_PAD_LEFT);
}
$node = base_convert($ipSix, 2, 10);
if (strlen($node) < 38) {
$node = null;
} else {
$node = crc32($node);
}
} elseif (empty($node)) {
$host = env('HOSTNAME');
if (empty($host)) {
$host = env('HOST');
}
if (!empty($host)) {
$ip = gethostbyname($host);
if ($ip === $host) {
$node = crc32($host);
} else {
$node = ip2long($ip);
}
}
} elseif ($node !== '127.0.0.1') {
$node = ip2long($node);
} else {
$node = null;
}
if (empty($node)) {
$node = crc32(Configure::read('Security.salt'));
}
if (function_exists('hphp_get_thread_id')) {
$pid = hphp_get_thread_id();
} elseif (function_exists('zend_thread_id')) {
$pid = zend_thread_id();
} else {
$pid = getmypid();
}
if (!$pid || $pid > 65535) {
$pid = mt_rand(0, 0xfff) | 0x4000;
}
list($timeMid, $timeLow) = explode(' ', microtime());
$uuid = sprintf(
"%08x-%04x-%04x-%02x%02x-%04x%08x", (int)$timeLow, (int)substr($timeMid, 2) & 0xffff,
mt_rand(0, 0xfff) | 0x4000, mt_rand(0, 0x3f) | 0x80, mt_rand(0, 0xff), $pid, $node
);
return $uuid;
}
/**
* Tokenizes a string using $separator, ignoring any instance of $separator that appears between
* $leftBound and $rightBound
*
* @param string $data The data to tokenize
* @param string $separator The token to split the data on.
* @param string $leftBound The left boundary to ignore separators in.
* @param string $rightBound The right boundary to ignore separators in.
* @return array Array of tokens in $data.
*/
public static function tokenize($data, $separator = ',', $leftBound = '(', $rightBound = ')') {
if (empty($data) || is_array($data)) {
return $data;
}
$depth = 0;
$offset = 0;
$buffer = '';
$results = array();
$length = strlen($data);
$open = false;
while ($offset <= $length) {
$tmpOffset = -1;
$offsets = array(
strpos($data, $separator, $offset),
strpos($data, $leftBound, $offset),
strpos($data, $rightBound, $offset)
);
for ($i = 0; $i < 3; $i++) {
if ($offsets[$i] !== false && ($offsets[$i] < $tmpOffset || $tmpOffset == -1)) {
$tmpOffset = $offsets[$i];
}
}
if ($tmpOffset !== -1) {
$buffer .= substr($data, $offset, ($tmpOffset - $offset));
if ($data{$tmpOffset} == $separator && $depth == 0) {
$results[] = $buffer;
$buffer = '';
} else {
$buffer .= $data{$tmpOffset};
}
if ($leftBound != $rightBound) {
if ($data{$tmpOffset} == $leftBound) {
$depth++;
}
if ($data{$tmpOffset} == $rightBound) {
$depth--;
}
} else {
if ($data{$tmpOffset} == $leftBound) {
if (!$open) {
$depth++;
$open = true;
} else {
$depth--;
$open = false;
}
}
}
$offset = ++$tmpOffset;
} else {
$results[] = $buffer . substr($data, $offset);
$offset = $length + 1;
}
}
if (empty($results) && !empty($buffer)) {
$results[] = $buffer;
}
if (!empty($results)) {
$data = array_map('trim', $results);
} else {
$data = array();
}
return $data;
}
/**
* Replaces variable placeholders inside a $str with any given $data. Each key in the $data array
* corresponds to a variable placeholder name in $str.
* Example: `String::insert(':name is :age years old.', array('name' => 'Bob', '65'));`
* Returns: Bob is 65 years old.
*
* Available $options are:
*
* - before: The character or string in front of the name of the variable placeholder (Defaults to `:`)
* - after: The character or string after the name of the variable placeholder (Defaults to null)
* - escape: The character or string used to escape the before character / string (Defaults to `\`)
* - format: A regex to use for matching variable placeholders. Default is: `/(?<!\\)\:%s/`
* (Overwrites before, after, breaks escape / clean)
* - clean: A boolean or array with instructions for String::cleanInsert
*
* @param string $str A string containing variable placeholders
* @param string $data A key => val array where each key stands for a placeholder variable name
* to be replaced with val
* @param string $options An array of options, see description above
* @return string
*/
public static function insert($str, $data, $options = array()) {
$defaults = array(
'before' => ':', 'after' => null, 'escape' => '\\', 'format' => null, 'clean' => false
);
$options += $defaults;
$format = $options['format'];
$data = (array)$data;
if (empty($data)) {
return ($options['clean']) ? String::cleanInsert($str, $options) : $str;
}
if (!isset($format)) {
$format = sprintf(
'/(?<!%s)%s%%s%s/',
preg_quote($options['escape'], '/'),
str_replace('%', '%%', preg_quote($options['before'], '/')),
str_replace('%', '%%', preg_quote($options['after'], '/'))
);
}
if (strpos($str, '?') !== false && is_numeric(key($data))) {
$offset = 0;
while (($pos = strpos($str, '?', $offset)) !== false) {
$val = array_shift($data);
$offset = $pos + strlen($val);
$str = substr_replace($str, $val, $pos, 1);
}
return ($options['clean']) ? String::cleanInsert($str, $options) : $str;
} else {
asort($data);
$hashKeys = array();
foreach ($data as $key => $value) {
$hashKeys[] = crc32($key);
}
$tempData = array_combine(array_keys($data), array_values($hashKeys));
krsort($tempData);
foreach ($tempData as $key => $hashVal) {
$key = sprintf($format, preg_quote($key, '/'));
$str = preg_replace($key, $hashVal, $str);
}
$dataReplacements = array_combine($hashKeys, array_values($data));
foreach ($dataReplacements as $tmpHash => $tmpValue) {
$tmpValue = (is_array($tmpValue)) ? '' : $tmpValue;
$str = str_replace($tmpHash, $tmpValue, $str);
}
}
if (!isset($options['format']) && isset($options['before'])) {
$str = str_replace($options['escape'] . $options['before'], $options['before'], $str);
}
return ($options['clean']) ? String::cleanInsert($str, $options) : $str;
}
/**
* Cleans up a String::insert() formatted string with given $options depending on the 'clean' key in
* $options. The default method used is text but html is also available. The goal of this function
* is to replace all whitespace and unneeded markup around placeholders that did not get replaced
* by String::insert().
*
* @param string $str
* @param string $options
* @return string
* @see String::insert()
*/
public static function cleanInsert($str, $options) {
$clean = $options['clean'];
if (!$clean) {
return $str;
}
if ($clean === true) {
$clean = array('method' => 'text');
}
if (!is_array($clean)) {
$clean = array('method' => $options['clean']);
}
switch ($clean['method']) {
case 'html':
$clean = array_merge(array(
'word' => '[\w,.]+',
'andText' => true,
'replacement' => '',
), $clean);
$kleenex = sprintf(
'/[\s]*[a-z]+=(")(%s%s%s[\s]*)+\\1/i',
preg_quote($options['before'], '/'),
$clean['word'],
preg_quote($options['after'], '/')
);
$str = preg_replace($kleenex, $clean['replacement'], $str);
if ($clean['andText']) {
$options['clean'] = array('method' => 'text');
$str = String::cleanInsert($str, $options);
}
break;
case 'text':
$clean = array_merge(array(
'word' => '[\w,.]+',
'gap' => '[\s]*(?:(?:and|or)[\s]*)?',
'replacement' => '',
), $clean);
$kleenex = sprintf(
'/(%s%s%s%s|%s%s%s%s)/',
preg_quote($options['before'], '/'),
$clean['word'],
preg_quote($options['after'], '/'),
$clean['gap'],
$clean['gap'],
preg_quote($options['before'], '/'),
$clean['word'],
preg_quote($options['after'], '/')
);
$str = preg_replace($kleenex, $clean['replacement'], $str);
break;
}
return $str;
}
/**
* Wraps text to a specific width, can optionally wrap at word breaks.
*
* ### Options
*
* - `width` The width to wrap to. Defaults to 72
* - `wordWrap` Only wrap on words breaks (spaces) Defaults to true.
* - `indent` String to indent with. Defaults to null.
* - `indentAt` 0 based index to start indenting at. Defaults to 0.
*
* @param string $text Text the text to format.
* @param array|integer $options Array of options to use, or an integer to wrap the text to.
* @return string Formatted text.
*/
public static function wrap($text, $options = array()) {
if (is_numeric($options)) {
$options = array('width' => $options);
}
$options += array('width' => 72, 'wordWrap' => true, 'indent' => null, 'indentAt' => 0);
if ($options['wordWrap']) {
$wrapped = wordwrap($text, $options['width'], "\n");
} else {
$wrapped = trim(chunk_split($text, $options['width'] - 1, "\n"));
}
if (!empty($options['indent'])) {
$chunks = explode("\n", $wrapped);
for ($i = $options['indentAt'], $len = count($chunks); $i < $len; $i++) {
$chunks[$i] = $options['indent'] . $chunks[$i];
}
$wrapped = implode("\n", $chunks);
}
return $wrapped;
}
/**
* Highlights a given phrase in a text. You can specify any expression in highlighter that
* may include the \1 expression to include the $phrase found.
*
* ### Options:
*
* - `format` The piece of html with that the phrase will be highlighted
* - `html` If true, will ignore any HTML tags, ensuring that only the correct text is highlighted
* - `regex` a custom regex rule that is ued to match words, default is '|$tag|iu'
*
* @param string $text Text to search the phrase in
* @param string $phrase The phrase that will be searched
* @param array $options An array of html attributes and options.
* @return string The highlighted text
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::highlight
*/
public static function highlight($text, $phrase, $options = array()) {
if (empty($phrase)) {
return $text;
}
$default = array(
'format' => '<span class="highlight">\1</span>',
'html' => false,
'regex' => "|%s|iu"
);
$options = array_merge($default, $options);
extract($options);
if (is_array($phrase)) {
$replace = array();
$with = array();
foreach ($phrase as $key => $segment) {
$segment = '(' . preg_quote($segment, '|') . ')';
if ($html) {
$segment = "(?![^<]+>)$segment(?![^<]+>)";
}
$with[] = (is_array($format)) ? $format[$key] : $format;
$replace[] = sprintf($options['regex'], $segment);
}
return preg_replace($replace, $with, $text);
} else {
$phrase = '(' . preg_quote($phrase, '|') . ')';
if ($html) {
$phrase = "(?![^<]+>)$phrase(?![^<]+>)";
}
return preg_replace(sprintf($options['regex'], $phrase), $format, $text);
}
}
/**
* Strips given text of all links (<a href=....)
*
* @param string $text Text
* @return string The text without links
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::stripLinks
*/
public static function stripLinks($text) {
return preg_replace('|<a\s+[^>]+>|im', '', preg_replace('|<\/a>|im', '', $text));
}
/**
* Truncates text.
*
* Cuts a string to the length of $length and replaces the last characters
* with the ending if the text is longer than length.
*
* ### Options:
*
* - `ending` Will be used as Ending and appended to the trimmed string
* - `exact` If false, $text will not be cut mid-word
* - `html` If true, HTML tags would be handled correctly
*
* @param string $text String to truncate.
* @param integer $length Length of returned string, including ellipsis.
* @param array $options An array of html attributes and options.
* @return string Trimmed string.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::truncate
*/
public static function truncate($text, $length = 100, $options = array()) {
$default = array(
'ending' => '...', 'exact' => true, 'html' => false
);
$options = array_merge($default, $options);
extract($options);
if (!function_exists('mb_strlen')) {
class_exists('Multibyte');
}
if ($html) {
if (mb_strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
return $text;
}
$totalLength = mb_strlen(strip_tags($ending));
$openTags = array();
$truncate = '';
preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER);
foreach ($tags as $tag) {
if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])) {
if (preg_match('/<[\w]+[^>]*>/s', $tag[0])) {
array_unshift($openTags, $tag[2]);
} elseif (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag)) {
$pos = array_search($closeTag[1], $openTags);
if ($pos !== false) {
array_splice($openTags, $pos, 1);
}
}
}
$truncate .= $tag[1];
$contentLength = mb_strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $tag[3]));
if ($contentLength + $totalLength > $length) {
$left = $length - $totalLength;
$entitiesLength = 0;
if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE)) {
foreach ($entities[0] as $entity) {
if ($entity[1] + 1 - $entitiesLength <= $left) {
$left--;
$entitiesLength += mb_strlen($entity[0]);
} else {
break;
}
}
}
$truncate .= mb_substr($tag[3], 0 , $left + $entitiesLength);
break;
} else {
$truncate .= $tag[3];
$totalLength += $contentLength;
}
if ($totalLength >= $length) {
break;
}
}
} else {
if (mb_strlen($text) <= $length) {
return $text;
} else {
$truncate = mb_substr($text, 0, $length - mb_strlen($ending));
}
}
if (!$exact) {
$spacepos = mb_strrpos($truncate, ' ');
if ($html) {
$truncateCheck = mb_substr($truncate, 0, $spacepos);
$lastOpenTag = mb_strrpos($truncateCheck, '<');
$lastCloseTag = mb_strrpos($truncateCheck, '>');
if ($lastOpenTag > $lastCloseTag) {
preg_match_all('/<[\w]+[^>]*>/s', $truncate, $lastTagMatches);
$lastTag = array_pop($lastTagMatches[0]);
$spacepos = mb_strrpos($truncate, $lastTag) + mb_strlen($lastTag);
}
$bits = mb_substr($truncate, $spacepos);
preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER);
if (!empty($droppedTags)) {
if (!empty($openTags)) {
foreach ($droppedTags as $closingTag) {
if (!in_array($closingTag[1], $openTags)) {
array_unshift($openTags, $closingTag[1]);
}
}
} else {
foreach ($droppedTags as $closingTag) {
$openTags[] = $closingTag[1];
}
}
}
}
$truncate = mb_substr($truncate, 0, $spacepos);
}
$truncate .= $ending;
if ($html) {
foreach ($openTags as $tag) {
$truncate .= '</' . $tag . '>';
}
}
return $truncate;
}
/**
* Extracts an excerpt from the text surrounding the phrase with a number of characters on each side
* determined by radius.
*
* @param string $text String to search the phrase in
* @param string $phrase Phrase that will be searched for
* @param integer $radius The amount of characters that will be returned on each side of the founded phrase
* @param string $ending Ending that will be appended
* @return string Modified string
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::excerpt
*/
public static function excerpt($text, $phrase, $radius = 100, $ending = '...') {
if (empty($text) || empty($phrase)) {
return self::truncate($text, $radius * 2, array('ending' => $ending));
}
$append = $prepend = $ending;
$phraseLen = mb_strlen($phrase);
$textLen = mb_strlen($text);
$pos = mb_strpos(mb_strtolower($text), mb_strtolower($phrase));
if ($pos === false) {
return mb_substr($text, 0, $radius) . $ending;
}
$startPos = $pos - $radius;
if ($startPos <= 0) {
$startPos = 0;
$prepend = '';
}
$endPos = $pos + $phraseLen + $radius;
if ($endPos >= $textLen) {
$endPos = $textLen;
$append = '';
}
$excerpt = mb_substr($text, $startPos, $endPos - $startPos);
$excerpt = $prepend . $excerpt . $append;
return $excerpt;
}
/**
* Creates a comma separated list where the last two items are joined with 'and', forming natural English
*
* @param array $list The list to be joined
* @param string $and The word used to join the last and second last items together with. Defaults to 'and'
* @param string $separator The separator used to join all the other items together. Defaults to ', '
* @return string The glued together string.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::toList
*/
public static function toList($list, $and = 'and', $separator = ', ') {
if (count($list) > 1) {
return implode($separator, array_slice($list, null, -1)) . ' ' . $and . ' ' . array_pop($list);
} else {
return array_pop($list);
}
}
class String extends CakeText {
}

File diff suppressed because it is too large Load diff

View file

@ -4,24 +4,24 @@
*
* The methods in these classes enable the datasources that use XML to work.
*
* PHP 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Utility
* @since CakePHP v .0.10.3.1400
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
App::uses('HttpSocket', 'Network/Http');
/**
* XML handling for Cake.
* XML handling for CakePHP.
*
* The methods in these classes enable the datasources that use XML to work.
*
@ -52,7 +52,7 @@ class Xml {
*
* Building from an array:
*
* {{{
* ```
* $value = array(
* 'tags' => array(
* 'tag' => array(
@ -68,18 +68,23 @@ class Xml {
* )
* );
* $xml = Xml::build($value);
* }}}
* ```
*
* When building XML from an array ensure that there is only one top level element.
*
* ### Options
*
* - `return` Can be 'simplexml' to return object of SimpleXMLElement or 'domdocument' to return DOMDocument.
* - `loadEntities` Defaults to false. Set to true to enable loading of `<!ENTITY` definitions. This
* - `loadEntities` Defaults to false. Set to true to enable loading of `<!ENTITY` definitions. This
* is disabled by default for security reasons.
* - If using array as input, you can pass `options` from Xml::fromArray.
* - `readFile` Set to false to disable file reading. This is important to disable when
* putting user data into Xml::build(). If enabled local & remote files will be read if they exist.
* Defaults to true for backwards compatibility reasons.
* - `parseHuge` Enable the `LIBXML_PARSEHUGE`
*
* @param string|array $input XML string, a path to a file, an URL or an array
* If using array as input, you can pass `options` from Xml::fromArray.
*
* @param string|array $input XML string, a path to a file, a URL or an array
* @param array $options The options to use
* @return SimpleXMLElement|DOMDocument SimpleXMLElement or DOMDocument
* @throws XmlException
@ -91,22 +96,28 @@ class Xml {
$defaults = array(
'return' => 'simplexml',
'loadEntities' => false,
'readFile' => true,
'parseHuge' => true
);
$options = array_merge($defaults, $options);
$options += $defaults;
if (is_array($input) || is_object($input)) {
return self::fromArray((array)$input, $options);
return static::fromArray((array)$input, $options);
} elseif (strpos($input, '<') !== false) {
return self::_loadXml($input, $options);
} elseif (file_exists($input)) {
return self::_loadXml(file_get_contents($input), $options);
} elseif (strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0) {
$socket = new HttpSocket();
$response = $socket->get($input);
if (!$response->isOk()) {
return static::_loadXml($input, $options);
} elseif ($options['readFile'] && file_exists($input)) {
return static::_loadXml(file_get_contents($input), $options);
} elseif ($options['readFile'] && strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0) {
try {
$socket = new HttpSocket(array('request' => array('redirect' => 10)));
$response = $socket->get($input);
if (!$response->isOk()) {
throw new XmlException(__d('cake_dev', 'XML cannot be read.'));
}
return static::_loadXml($response->body, $options);
} catch (SocketException $e) {
throw new XmlException(__d('cake_dev', 'XML cannot be read.'));
}
return self::_loadXml($response->body, $options);
} elseif (!is_string($input)) {
throw new XmlException(__d('cake_dev', 'Invalid input.'));
}
@ -117,8 +128,9 @@ class Xml {
* Parse the input data and create either a SimpleXmlElement object or a DOMDocument.
*
* @param string $input The input to load.
* @param array $options The options to use. See Xml::build()
* @return SimpleXmlElement|DOMDocument.
* @param array $options The options to use. See Xml::build()
* @return SimpleXmlElement|DOMDocument
* @throws XmlException
*/
protected static function _loadXml($input, $options) {
$hasDisable = function_exists('libxml_disable_entity_loader');
@ -126,16 +138,27 @@ class Xml {
if ($hasDisable && !$options['loadEntities']) {
libxml_disable_entity_loader(true);
}
if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
$xml = new SimpleXMLElement($input, LIBXML_NOCDATA);
} else {
$xml = new DOMDocument();
$xml->loadXML($input);
$flags = LIBXML_NOCDATA;
if (!empty($options['parseHuge'])) {
$flags |= LIBXML_PARSEHUGE;
}
try {
if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
$xml = new SimpleXMLElement($input, LIBXML_NOCDATA);
} else {
$xml = new DOMDocument();
$xml->loadXML($input);
}
} catch (Exception $e) {
$xml = null;
}
if ($hasDisable && !$options['loadEntities']) {
libxml_disable_entity_loader(false);
}
libxml_use_internal_errors($internalErrors);
if ($xml === null) {
throw new XmlException(__d('cake_dev', 'Xml cannot be read.'));
}
return $xml;
}
@ -144,14 +167,15 @@ class Xml {
*
* ### Options
*
* - `format` If create childs ('tags') or attributes ('attribute').
* - `format` If create childs ('tags') or attributes ('attributes').
* - `pretty` Returns formatted Xml when set to `true`. Defaults to `false`
* - `version` Version of XML document. Default is 1.0.
* - `encoding` Encoding of XML document. If null remove from XML header. Default is the some of application.
* - `return` If return object of SimpleXMLElement ('simplexml') or DOMDocument ('domdocument'). Default is SimpleXMLElement.
*
* Using the following data:
*
* {{{
* ```
* $value = array(
* 'root' => array(
* 'tag' => array(
@ -161,13 +185,13 @@ class Xml {
* )
* )
* );
* }}}
* ```
*
* Calling `Xml::fromArray($value, 'tags');` Will generate:
*
* `<root><tag><id>1</id><value>defect</value>description</tag></root>`
*
* And calling `Xml::fromArray($value, 'attribute');` Will generate:
* And calling `Xml::fromArray($value, 'attributes');` Will generate:
*
* `<root><tag id="1" value="defect">description</tag></root>`
*
@ -181,7 +205,7 @@ class Xml {
throw new XmlException(__d('cake_dev', 'Invalid input.'));
}
$key = key($input);
if (is_integer($key)) {
if (is_int($key)) {
throw new XmlException(__d('cake_dev', 'The key of input must be alphanumeric'));
}
@ -192,12 +216,16 @@ class Xml {
'format' => 'tags',
'version' => '1.0',
'encoding' => Configure::read('App.encoding'),
'return' => 'simplexml'
'return' => 'simplexml',
'pretty' => false
);
$options = array_merge($defaults, $options);
$options += $defaults;
$dom = new DOMDocument($options['version'], $options['encoding']);
self::_fromArray($dom, $dom, $input, $options['format']);
if ($options['pretty']) {
$dom->formatOutput = true;
}
static::_fromArray($dom, $dom, $input, $options['format']);
$options['return'] = strtolower($options['return']);
if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
@ -211,8 +239,8 @@ class Xml {
*
* @param DOMDocument $dom Handler to DOMDocument
* @param DOMElement $node Handler to DOMElement (child)
* @param array $data Array of data to append to the $node.
* @param string $format Either 'attribute' or 'tags'. This determines where nested keys go.
* @param array &$data Array of data to append to the $node.
* @param string $format Either 'attributes' or 'tags'. This determines where nested keys go.
* @return void
* @throws XmlException
*/
@ -261,10 +289,10 @@ class Xml {
foreach ($value as $item) {
$itemData = compact('dom', 'node', 'key', 'format');
$itemData['value'] = $item;
self::_createChild($itemData);
static::_createChild($itemData);
}
} else { // Struct
self::_createChild(compact('dom', 'node', 'key', 'value', 'format'));
static::_createChild(compact('dom', 'node', 'key', 'value', 'format'));
}
}
} else {
@ -291,25 +319,24 @@ class Xml {
$childNS = $value['xmlns:'];
unset($value['xmlns:']);
}
} elseif (!empty($value) || $value === 0) {
} elseif (!empty($value) || $value === 0 || $value === '0') {
$childValue = (string)$value;
}
if ($childValue) {
$child = $dom->createElement($key, $childValue);
} else {
$child = $dom->createElement($key);
$child = $dom->createElement($key);
if ($childValue !== null) {
$child->appendChild($dom->createTextNode($childValue));
}
if ($childNS) {
$child->setAttribute('xmlns', $childNS);
}
self::_fromArray($dom, $child, $value, $format);
static::_fromArray($dom, $child, $value, $format);
$node->appendChild($child);
}
/**
* Returns this XML structure as a array.
* Returns this XML structure as an array.
*
* @param SimpleXMLElement|DOMDocument|DOMNode $obj SimpleXMLElement, DOMDocument or DOMNode instance
* @return array Array representation of the XML structure.
@ -324,7 +351,7 @@ class Xml {
}
$result = array();
$namespaces = array_merge(array('' => ''), $obj->getNamespaces(true));
self::_toArray($obj, $result, '', array_keys($namespaces));
static::_toArray($obj, $result, '', array_keys($namespaces));
return $result;
}
@ -332,7 +359,7 @@ class Xml {
* Recursive method to toArray
*
* @param SimpleXMLElement $xml SimpleXMLElement object
* @param array $parentData Parent array with data
* @param array &$parentData Parent array with data
* @param string $ns Namespace of current child
* @param array $namespaces List of namespaces in XML
* @return void
@ -349,14 +376,14 @@ class Xml {
}
foreach ($xml->children($namespace, true) as $child) {
self::_toArray($child, $data, $namespace, $namespaces);
static::_toArray($child, $data, $namespace, $namespaces);
}
}
$asString = trim((string)$xml);
if (empty($data)) {
$data = $asString;
} elseif (!empty($asString)) {
} elseif (strlen($asString) > 0) {
$data['@'] = $asString;
}