mirror of
https://github.com/brmlab/brmsklad.git
synced 2025-10-29 23:33:58 +01:00
Backup of current cakephp version
This commit is contained in:
parent
b8f82da6f8
commit
5a580df460
925 changed files with 238041 additions and 1 deletions
299
lib/Cake/Utility/CakeNumber.php
Normal file
299
lib/Cake/Utility/CakeNumber.php
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
<?php
|
||||
/**
|
||||
* CakeNumber Utility.
|
||||
*
|
||||
* 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)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright 2005-2012, 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)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Number helper library.
|
||||
*
|
||||
* Methods to make numbers more readable.
|
||||
*
|
||||
* @package Cake.Utility
|
||||
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html
|
||||
*/
|
||||
class CakeNumber {
|
||||
|
||||
/**
|
||||
* Currencies supported by the helper. You can add additional currency formats
|
||||
* with CakeNumber::addFormat
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_currencies = array(
|
||||
'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
|
||||
),
|
||||
'EUR' => array(
|
||||
'wholeSymbol' => '€', 'wholePosition' => 'before', 'fractionSymbol' => false, 'fractionPosition' => 'after',
|
||||
'zero' => 0, 'places' => 2, 'thousands' => '.', 'decimals' => ',', 'negative' => '()', 'escape' => false
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Default options for currency formats
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_currencyDefaults = array(
|
||||
'wholeSymbol' => '', 'wholePosition' => 'before', 'fractionSymbol' => '', 'fractionPosition' => 'after',
|
||||
'zero' => '0', 'places' => 2, 'thousands' => ',', 'decimals' => '.','negative' => '()', 'escape' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* If native number_format() should be used. If >= PHP5.4
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
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.
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted-for-humans file size.
|
||||
*
|
||||
* @param integer $size Size in bytes
|
||||
* @return string Human readable size
|
||||
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::toReadableSize
|
||||
*/
|
||||
public static function toReadableSize($size) {
|
||||
switch (true) {
|
||||
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));
|
||||
case round($size / 1024 / 1024, 2) < 1024:
|
||||
return __d('cake', '%.2f MB', self::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));
|
||||
default:
|
||||
return __d('cake', '%.2f TB', self::precision($size / 1024 / 1024 / 1024 / 1024, 2));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a number into a percentage string.
|
||||
*
|
||||
* @param float $number A floating point number
|
||||
* @param integer $precision The precision of the returned number
|
||||
* @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) . '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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) {
|
||||
$places = 0;
|
||||
if (is_int($options)) {
|
||||
$places = $options;
|
||||
}
|
||||
|
||||
$separators = array(',', '.', '-', ':');
|
||||
|
||||
$before = $after = null;
|
||||
if (is_string($options) && !in_array($options, $separators)) {
|
||||
$before = $options;
|
||||
}
|
||||
$thousands = ',';
|
||||
if (!is_array($options) && in_array($options, $separators)) {
|
||||
$thousands = $options;
|
||||
}
|
||||
$decimals = '.';
|
||||
if (!is_array($options) && in_array($options, $separators)) {
|
||||
$decimals = $options;
|
||||
}
|
||||
|
||||
$escape = true;
|
||||
if (is_array($options)) {
|
||||
$options = array_merge(array('before' => '$', 'places' => 2, 'thousands' => ',', 'decimals' => '.'), $options);
|
||||
extract($options);
|
||||
}
|
||||
|
||||
$out = $before . self::_numberFormat($number, $places, $decimals, $thousands) . $after;
|
||||
|
||||
if ($escape) {
|
||||
return h($out);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative number_format() to accommodate multibyte decimals and thousands < PHP 5.4
|
||||
*
|
||||
* @param float $number
|
||||
* @param integer $places
|
||||
* @param string $decimals
|
||||
* @param string $thousands
|
||||
* @return string
|
||||
*/
|
||||
protected static function _numberFormat($number, $places = 0, $decimals = '.', $thousands = ',') {
|
||||
if (!isset(self::$_numberFormatSupport)) {
|
||||
self::$_numberFormatSupport = version_compare(PHP_VERSION, '5.4.0', '>=');
|
||||
}
|
||||
if (self::$_numberFormatSupport) {
|
||||
return number_format($number, $places, $decimals, $thousands);
|
||||
}
|
||||
$number = number_format($number, $places, '.', '');
|
||||
$after = '';
|
||||
$foundDecimal = strpos($number, '.');
|
||||
if ($foundDecimal !== false) {
|
||||
$after = substr($number, $foundDecimal);
|
||||
$number = substr($number, 0, $foundDecimal);
|
||||
}
|
||||
while (($foundThousand = preg_replace('/(\d+)(\d\d\d)/', '\1 \2', $number)) != $number) {
|
||||
$number = $foundThousand;
|
||||
}
|
||||
$number .= $after;
|
||||
return strtr($number, array(' ' => $thousands, '.' => $decimals));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a number into a currency format.
|
||||
*
|
||||
* ### Options
|
||||
*
|
||||
* - `wholeSymbol` - The currency symbol to use for whole numbers,
|
||||
* greater than 1, or less than -1.
|
||||
* - `wholePosition` - The position the whole symbol should be placed
|
||||
* valid options are 'before' & 'after'.
|
||||
* - `fractionSymbol` - The currency symbol to use for fractional numbers.
|
||||
* - `fractionPosition` - The position the fraction symbol should be placed
|
||||
* valid options are 'before' & 'after'.
|
||||
* - `before` - The currency symbol to place before whole numbers
|
||||
* 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`
|
||||
* - `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
|
||||
* - `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.
|
||||
*
|
||||
* @param float $number
|
||||
* @param string $currency Shortcut to default options. Valid values are
|
||||
* 'USD', 'EUR', 'GBP', otherwise set at least 'before' and 'after' options.
|
||||
* @param array $options
|
||||
* @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;
|
||||
|
||||
if (isset(self::$_currencies[$currency])) {
|
||||
$default = self::$_currencies[$currency];
|
||||
} elseif (is_string($currency)) {
|
||||
$options['before'] = $currency;
|
||||
}
|
||||
|
||||
$options = array_merge($default, $options);
|
||||
|
||||
if (isset($options['before']) && $options['before'] !== '') {
|
||||
$options['wholeSymbol'] = $options['before'];
|
||||
}
|
||||
if (isset($options['after']) && !$options['after'] !== '') {
|
||||
$options['fractionSymbol'] = $options['after'];
|
||||
}
|
||||
|
||||
$result = $options['before'] = $options['after'] = null;
|
||||
|
||||
$symbolKey = 'whole';
|
||||
if ($number == 0 ) {
|
||||
if ($options['zero'] !== 0 ) {
|
||||
return $options['zero'];
|
||||
}
|
||||
} elseif ($number < 1 && $number > -1 ) {
|
||||
if ($options['fractionSymbol'] !== false) {
|
||||
$multiply = intval('1' . str_pad('', $options['places'], '0'));
|
||||
$number = $number * $multiply;
|
||||
$options['places'] = null;
|
||||
$symbolKey = 'fraction';
|
||||
}
|
||||
}
|
||||
|
||||
$position = $options[$symbolKey . 'Position'] != 'after' ? 'before' : 'after';
|
||||
$options[$position] = $options[$symbolKey . 'Symbol'];
|
||||
|
||||
$abs = abs($number);
|
||||
$result = self::format($abs, $options);
|
||||
|
||||
if ($number < 0 ) {
|
||||
if ($options['negative'] == '()') {
|
||||
$result = '(' . $result . ')';
|
||||
} else {
|
||||
$result = $options['negative'] . $result;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a currency format to the Number helper. Makes reusing
|
||||
* currency formats easier.
|
||||
*
|
||||
* {{{ $number->addFormat('NOK', array('before' => 'Kr. ')); }}}
|
||||
*
|
||||
* You can now use `NOK` as a shortform when formatting currency amounts.
|
||||
*
|
||||
* {{{ $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.
|
||||
*
|
||||
* @param string $formatName The format name to be used in the future.
|
||||
* @param array $options The array of options for this format.
|
||||
* @return void
|
||||
* @see NumberHelper::currency()
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
||||
1055
lib/Cake/Utility/CakeTime.php
Normal file
1055
lib/Cake/Utility/CakeTime.php
Normal file
File diff suppressed because it is too large
Load diff
368
lib/Cake/Utility/ClassRegistry.php
Normal file
368
lib/Cake/Utility/ClassRegistry.php
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright 2005-2012, 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)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Included libraries.
|
||||
*/
|
||||
App::uses('Model', 'Model');
|
||||
App::uses('AppModel', 'Model');
|
||||
App::uses('ConnectionManager', 'Model');
|
||||
|
||||
/**
|
||||
* Class Collections.
|
||||
*
|
||||
* A repository for class objects, each registered with a key.
|
||||
* If you try to add an object with the same key twice, nothing will come of it.
|
||||
* If you need a second instance of an object, give it another key.
|
||||
*
|
||||
* @package Cake.Utility
|
||||
*/
|
||||
class ClassRegistry {
|
||||
|
||||
/**
|
||||
* Names of classes with their objects.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_objects = array();
|
||||
|
||||
/**
|
||||
* Names of class names mapped to the object in the registry.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_map = array();
|
||||
|
||||
/**
|
||||
* Default constructor parameter settings, indexed by type
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_config = array();
|
||||
|
||||
/**
|
||||
* Return a singleton instance of the ClassRegistry.
|
||||
*
|
||||
* @return ClassRegistry instance
|
||||
*/
|
||||
public static function &getInstance() {
|
||||
static $instance = array();
|
||||
if (!$instance) {
|
||||
$instance[0] = new ClassRegistry();
|
||||
}
|
||||
return $instance[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a class, registers the object in the registry and returns instance of the object. ClassRegistry::init()
|
||||
* is used as a factory for models, and handle correct injecting of settings, that assist in testing.
|
||||
*
|
||||
* Examples
|
||||
* Simple Use: Get a Post model instance ```ClassRegistry::init('Post');```
|
||||
*
|
||||
* Expanded: ```array('class' => 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry', 'type' => 'Model');```
|
||||
*
|
||||
* 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
|
||||
* of trying to create an AppModel
|
||||
* @return object 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;
|
||||
if (!isset($class[0])) {
|
||||
$objects = array($class);
|
||||
}
|
||||
} else {
|
||||
$objects = array(array('class' => $class));
|
||||
}
|
||||
$defaults = isset($_this->_config['Model']) ? $_this->_config['Model'] : array();
|
||||
$count = count($objects);
|
||||
$availableDs = array_keys(ConnectionManager::enumConnectionObjects());
|
||||
|
||||
foreach ($objects as $key => $settings) {
|
||||
if (is_array($settings)) {
|
||||
$pluginPath = null;
|
||||
$settings = array_merge($defaults, $settings);
|
||||
$class = $settings['class'];
|
||||
|
||||
list($plugin, $class) = pluginSplit($class);
|
||||
if ($plugin) {
|
||||
$pluginPath = $plugin . '.';
|
||||
}
|
||||
|
||||
if (empty($settings['alias'])) {
|
||||
$settings['alias'] = $class;
|
||||
}
|
||||
$alias = $settings['alias'];
|
||||
|
||||
if ($model = $_this->_duplicate($alias, $class)) {
|
||||
$_this->map($alias, $class);
|
||||
return $model;
|
||||
}
|
||||
|
||||
App::uses($plugin . 'AppModel', $pluginPath . 'Model');
|
||||
App::uses($class, $pluginPath . 'Model');
|
||||
|
||||
if (class_exists($class) || interface_exists($class)) {
|
||||
$reflection = new ReflectionClass($class);
|
||||
if ($reflection->isAbstract() || $reflection->isInterface()) {
|
||||
throw new CakeException(__d('cake_dev', 'Cannot create instance of %s, as it is abstract or is an interface', $class));
|
||||
}
|
||||
$testing = isset($settings['testing']) ? $settings['testing'] : false;
|
||||
if ($testing) {
|
||||
$settings['ds'] = 'test';
|
||||
$defaultProperties = $reflection->getDefaultProperties();
|
||||
if (isset($defaultProperties['useDbConfig'])) {
|
||||
$useDbConfig = $defaultProperties['useDbConfig'];
|
||||
if (in_array('test_' . $useDbConfig, $availableDs)) {
|
||||
$useDbConfig = 'test_' . $useDbConfig;
|
||||
}
|
||||
if (strpos($useDbConfig, 'test') === 0) {
|
||||
$settings['ds'] = $useDbConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($reflection->getConstructor()) {
|
||||
$instance = $reflection->newInstance($settings);
|
||||
} else {
|
||||
$instance = $reflection->newInstance();
|
||||
}
|
||||
if ($strict) {
|
||||
$instance = ($instance instanceof Model) ? $instance : null;
|
||||
}
|
||||
}
|
||||
if (!isset($instance)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
$_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 $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static function addObject($key, $object) {
|
||||
$_this = ClassRegistry::getInstance();
|
||||
$key = Inflector::underscore($key);
|
||||
if (!isset($_this->_objects[$key])) {
|
||||
$_this->_objects[$key] = $object;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove object which corresponds to given key.
|
||||
*
|
||||
* @param string $key Key of object to remove from registry
|
||||
* @return void
|
||||
*/
|
||||
public static function removeObject($key) {
|
||||
$_this = ClassRegistry::getInstance();
|
||||
$key = Inflector::underscore($key);
|
||||
if (isset($_this->_objects[$key])) {
|
||||
unset($_this->_objects[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all keys from the registry.
|
||||
*
|
||||
* @return array Set of keys stored in registry
|
||||
*/
|
||||
public static function keys() {
|
||||
$_this = ClassRegistry::getInstance();
|
||||
return array_keys($_this->_objects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return object which corresponds to given key.
|
||||
*
|
||||
* @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) {
|
||||
$_this = ClassRegistry::getInstance();
|
||||
$key = Inflector::underscore($key);
|
||||
$return = false;
|
||||
if (isset($_this->_objects[$key])) {
|
||||
$return = $_this->_objects[$key];
|
||||
} else {
|
||||
$key = $_this->_getMap($key);
|
||||
if (isset($_this->_objects[$key])) {
|
||||
$return = $_this->_objects[$key];
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default constructor parameter for an object type
|
||||
*
|
||||
* @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
|
||||
* the previously-set value of $param, or null if not set.
|
||||
*/
|
||||
public static function config($type, $param = array()) {
|
||||
$_this = ClassRegistry::getInstance();
|
||||
|
||||
if (empty($param) && is_array($type)) {
|
||||
$param = $type;
|
||||
$type = 'Model';
|
||||
} elseif (is_null($param)) {
|
||||
unset($_this->_config[$type]);
|
||||
} elseif (empty($param) && is_string($type)) {
|
||||
return isset($_this->_config[$type]) ? $_this->_config[$type] : null;
|
||||
}
|
||||
if (isset($_this->_config[$type]['testing'])) {
|
||||
$param['testing'] = true;
|
||||
}
|
||||
$_this->_config[$type] = $param;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if $alias is a duplicate $class Object
|
||||
*
|
||||
* @param string $alias
|
||||
* @param string $class
|
||||
* @return boolean
|
||||
*/
|
||||
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)) {
|
||||
$duplicate = $model;
|
||||
}
|
||||
unset($model);
|
||||
}
|
||||
return $duplicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a key name pair to the registry to map name to class in the registry.
|
||||
*
|
||||
* @param string $key Key to include in map
|
||||
* @param string $name Key that is being mapped
|
||||
* @return void
|
||||
*/
|
||||
public static function map($key, $name) {
|
||||
$_this = ClassRegistry::getInstance();
|
||||
$key = Inflector::underscore($key);
|
||||
$name = Inflector::underscore($name);
|
||||
if (!isset($_this->_map[$key])) {
|
||||
$_this->_map[$key] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all keys from the map in the registry.
|
||||
*
|
||||
* @return array Keys of registry's map
|
||||
*/
|
||||
public static function mapKeys() {
|
||||
$_this = ClassRegistry::getInstance();
|
||||
return array_keys($_this->_map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of a class in the registry.
|
||||
*
|
||||
* @param string $key Key to find in map
|
||||
* @return string Mapped value
|
||||
*/
|
||||
protected function _getMap($key) {
|
||||
if (isset($this->_map[$key])) {
|
||||
return $this->_map[$key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes all objects from the ClassRegistry.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function flush() {
|
||||
$_this = ClassRegistry::getInstance();
|
||||
$_this->_objects = array();
|
||||
$_this->_map = array();
|
||||
}
|
||||
|
||||
}
|
||||
833
lib/Cake/Utility/Debugger.php
Normal file
833
lib/Cake/Utility/Debugger.php
Normal file
|
|
@ -0,0 +1,833 @@
|
|||
<?php
|
||||
/**
|
||||
* Framework debugging and PHP error-handling class
|
||||
*
|
||||
* 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)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright 2005-2012, 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)
|
||||
*/
|
||||
|
||||
App::uses('CakeLog', 'Log');
|
||||
App::uses('String', 'Utility');
|
||||
|
||||
/**
|
||||
* Provide custom logging and error handling.
|
||||
*
|
||||
* Debugger overrides PHP's default error handling to provide stack traces and enhanced logging
|
||||
*
|
||||
* @package Cake.Utility
|
||||
* @link http://book.cakephp.org/2.0/en/development/debugging.html#debugger-class
|
||||
*/
|
||||
class Debugger {
|
||||
|
||||
/**
|
||||
* A list of errors generated by the application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $errors = array();
|
||||
|
||||
/**
|
||||
* The current output format.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_outputFormat = 'js';
|
||||
|
||||
/**
|
||||
* Templates used when generating trace or error strings. Can be global or indexed by the format
|
||||
* value used in $_outputFormat.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_templates = array(
|
||||
'log' => array(
|
||||
'trace' => '{:reference} - {:path}, line {:line}',
|
||||
'error' => "{:error} ({:code}): {:description} in [{:file}, line {:line}]"
|
||||
),
|
||||
'js' => array(
|
||||
'error' => '',
|
||||
'info' => '',
|
||||
'trace' => '<pre class="stack-trace">{:trace}</pre>',
|
||||
'code' => '',
|
||||
'context' => '',
|
||||
'links' => array(),
|
||||
'escapeContext' => true,
|
||||
),
|
||||
'html' => array(
|
||||
'trace' => '<pre class="cake-error trace"><b>Trace</b> <p>{:trace}</p></pre>',
|
||||
'context' => '<pre class="cake-error context"><b>Context</b> <p>{:context}</p></pre>',
|
||||
'escapeContext' => true,
|
||||
),
|
||||
'txt' => array(
|
||||
'error' => "{:error}: {:code} :: {:description} on line {:line} of {:path}\n{:info}",
|
||||
'code' => '',
|
||||
'info' => ''
|
||||
),
|
||||
'base' => array(
|
||||
'traceLine' => '{:reference} - {:path}, line {:line}',
|
||||
'trace' => "Trace:\n{:trace}\n",
|
||||
'context' => "Context:\n{:context}\n",
|
||||
),
|
||||
'log' => array(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds current output data when outputFormat is false.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_data = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*/
|
||||
public function __construct() {
|
||||
$docRef = ini_get('docref_root');
|
||||
|
||||
if (empty($docRef) && function_exists('ini_set')) {
|
||||
ini_set('docref_root', 'http://php.net/');
|
||||
}
|
||||
if (!defined('E_RECOVERABLE_ERROR')) {
|
||||
define('E_RECOVERABLE_ERROR', 4096);
|
||||
}
|
||||
|
||||
$e = '<pre class="cake-error">';
|
||||
$e .= '<a href="javascript:void(0);" onclick="document.getElementById(\'{:id}-trace\')';
|
||||
$e .= '.style.display = (document.getElementById(\'{:id}-trace\').style.display == ';
|
||||
$e .= '\'none\' ? \'\' : \'none\');"><b>{:error}</b> ({:code})</a>: {:description} ';
|
||||
$e .= '[<b>{:path}</b>, line <b>{:line}</b>]';
|
||||
|
||||
$e .= '<div id="{:id}-trace" class="cake-stack-trace" style="display: none;">';
|
||||
$e .= '{:links}{:info}</div>';
|
||||
$e .= '</pre>';
|
||||
$this->_templates['js']['error'] = $e;
|
||||
|
||||
$t = '<div id="{:id}-trace" class="cake-stack-trace" style="display: none;">';
|
||||
$t .= '{:context}{:code}{:trace}</div>';
|
||||
$this->_templates['js']['info'] = $t;
|
||||
|
||||
$links = array();
|
||||
$link = '<a href="javascript:void(0);" onclick="document.getElementById(\'{:id}-code\')';
|
||||
$link .= '.style.display = (document.getElementById(\'{:id}-code\').style.display == ';
|
||||
$link .= '\'none\' ? \'\' : \'none\')">Code</a>';
|
||||
$links['code'] = $link;
|
||||
|
||||
$link = '<a href="javascript:void(0);" onclick="document.getElementById(\'{:id}-context\')';
|
||||
$link .= '.style.display = (document.getElementById(\'{:id}-context\').style.display == ';
|
||||
$link .= '\'none\' ? \'\' : \'none\')">Context</a>';
|
||||
$links['context'] = $link;
|
||||
|
||||
$this->_templates['js']['links'] = $links;
|
||||
|
||||
$this->_templates['js']['context'] = '<pre id="{:id}-context" class="cake-context" ';
|
||||
$this->_templates['js']['context'] .= 'style="display: none;">{:context}</pre>';
|
||||
|
||||
$this->_templates['js']['code'] = '<pre id="{:id}-code" class="cake-code-dump" ';
|
||||
$this->_templates['js']['code'] .= 'style="display: none;">{:code}</pre>';
|
||||
|
||||
$e = '<pre class="cake-error"><b>{:error}</b> ({:code}) : {:description} ';
|
||||
$e .= '[<b>{:path}</b>, line <b>{:line}]</b></pre>';
|
||||
$this->_templates['html']['error'] = $e;
|
||||
|
||||
$this->_templates['html']['context'] = '<pre class="cake-context"><b>Context</b> ';
|
||||
$this->_templates['html']['context'] .= '<p>{:context}</p></pre>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the Debugger singleton object instance.
|
||||
*
|
||||
* @param string $class
|
||||
* @return object
|
||||
*/
|
||||
public static function &getInstance($class = null) {
|
||||
static $instance = array();
|
||||
if (!empty($class)) {
|
||||
if (!$instance || strtolower($class) != strtolower(get_class($instance[0]))) {
|
||||
$instance[0] = new $class();
|
||||
}
|
||||
}
|
||||
if (!$instance) {
|
||||
$instance[0] = new Debugger();
|
||||
}
|
||||
return $instance[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively formats and outputs the contents of the supplied variable.
|
||||
*
|
||||
*
|
||||
* @param mixed $var the variable to dump
|
||||
* @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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides PHP's default error handling.
|
||||
*
|
||||
* @param integer $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 array $context Context
|
||||
* @return boolean true if error was handled
|
||||
* @deprecated This function is superseded by Debugger::outputError()
|
||||
*/
|
||||
public static function showError($code, $description, $file = null, $line = null, $context = null) {
|
||||
$self = Debugger::getInstance();
|
||||
|
||||
if (empty($file)) {
|
||||
$file = '[internal]';
|
||||
}
|
||||
if (empty($line)) {
|
||||
$line = '??';
|
||||
}
|
||||
|
||||
$info = compact('code', 'description', 'file', 'line');
|
||||
if (!in_array($info, $self->errors)) {
|
||||
$self->errors[] = $info;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($code) {
|
||||
case E_PARSE:
|
||||
case E_ERROR:
|
||||
case E_CORE_ERROR:
|
||||
case E_COMPILE_ERROR:
|
||||
case E_USER_ERROR:
|
||||
$error = 'Fatal Error';
|
||||
$level = LOG_ERR;
|
||||
break;
|
||||
case E_WARNING:
|
||||
case E_USER_WARNING:
|
||||
case E_COMPILE_WARNING:
|
||||
case E_RECOVERABLE_ERROR:
|
||||
$error = 'Warning';
|
||||
$level = LOG_WARNING;
|
||||
break;
|
||||
case E_NOTICE:
|
||||
case E_USER_NOTICE:
|
||||
$error = 'Notice';
|
||||
$level = LOG_NOTICE;
|
||||
break;
|
||||
case E_DEPRECATED:
|
||||
case E_USER_DEPRECATED:
|
||||
$error = 'Deprecated';
|
||||
$level = LOG_NOTICE;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
$data = compact(
|
||||
'level', 'error', 'code', 'description', 'file', 'path', 'line', 'context'
|
||||
);
|
||||
echo $self->outputError($data);
|
||||
|
||||
if ($error == 'Fatal Error') {
|
||||
exit();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a stack trace based on the supplied options.
|
||||
*
|
||||
* ### 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 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
|
||||
*
|
||||
* @param array $options Format for outputting stack trace
|
||||
* @return mixed Formatted stack trace
|
||||
* @link http://book.cakephp.org/2.0/en/development/debugging.html#Debugger::trace
|
||||
*/
|
||||
public static function trace($options = array()) {
|
||||
$self = Debugger::getInstance();
|
||||
$defaults = array(
|
||||
'depth' => 999,
|
||||
'format' => $self->_outputFormat,
|
||||
'args' => false,
|
||||
'start' => 0,
|
||||
'scope' => null,
|
||||
'exclude' => array('call_user_func_array', 'trigger_error')
|
||||
);
|
||||
$options = Hash::merge($defaults, $options);
|
||||
|
||||
$backtrace = debug_backtrace();
|
||||
$count = count($backtrace);
|
||||
$back = array();
|
||||
|
||||
$_trace = array(
|
||||
'line' => '??',
|
||||
'file' => '[internal]',
|
||||
'class' => null,
|
||||
'function' => '[main]'
|
||||
);
|
||||
|
||||
for ($i = $options['start']; $i < $count && $i < $options['depth']; $i++) {
|
||||
$trace = array_merge(array('file' => '[internal]', 'line' => '??'), $backtrace[$i]);
|
||||
$signature = $reference = '[main]';
|
||||
|
||||
if (isset($backtrace[$i + 1])) {
|
||||
$next = array_merge($_trace, $backtrace[$i + 1]);
|
||||
$signature = $reference = $next['function'];
|
||||
|
||||
if (!empty($next['class'])) {
|
||||
$signature = $next['class'] . '::' . $next['function'];
|
||||
$reference = $signature . '(';
|
||||
if ($options['args'] && isset($next['args'])) {
|
||||
$args = array();
|
||||
foreach ($next['args'] as $arg) {
|
||||
$args[] = Debugger::exportVar($arg);
|
||||
}
|
||||
$reference .= join(', ', $args);
|
||||
}
|
||||
$reference .= ')';
|
||||
}
|
||||
}
|
||||
if (in_array($signature, $options['exclude'])) {
|
||||
continue;
|
||||
}
|
||||
if ($options['format'] == 'points' && $trace['file'] != '[internal]') {
|
||||
$back[] = array('file' => $trace['file'], 'line' => $trace['line']);
|
||||
} elseif ($options['format'] == 'array') {
|
||||
$back[] = $trace;
|
||||
} else {
|
||||
if (isset($self->_templates[$options['format']]['traceLine'])) {
|
||||
$tpl = $self->_templates[$options['format']]['traceLine'];
|
||||
} else {
|
||||
$tpl = $self->_templates['base']['traceLine'];
|
||||
}
|
||||
$trace['path'] = self::trimPath($trace['file']);
|
||||
$trace['reference'] = $reference;
|
||||
unset($trace['object'], $trace['args']);
|
||||
$back[] = String::insert($tpl, $trace, array('before' => '{:', 'after' => '}'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['format'] == 'array' || $options['format'] == 'points') {
|
||||
return $back;
|
||||
}
|
||||
return implode("\n", $back);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortens file paths by replacing the application base path with 'APP', and the CakePHP core
|
||||
* path with 'CORE'.
|
||||
*
|
||||
* @param string $path Path to shorten
|
||||
* @return string Normalized path
|
||||
*/
|
||||
public static function trimPath($path) {
|
||||
if (!defined('CAKE_CORE_INCLUDE_PATH') || !defined('APP')) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
if (strpos($path, APP) === 0) {
|
||||
return str_replace(APP, 'APP' . DS, $path);
|
||||
} elseif (strpos($path, CAKE_CORE_INCLUDE_PATH) === 0) {
|
||||
return str_replace(CAKE_CORE_INCLUDE_PATH, 'CORE', $path);
|
||||
} elseif (strpos($path, ROOT) === 0) {
|
||||
return str_replace(ROOT, 'ROOT', $path);
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs an excerpt from a file and highlights a given line of code.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* `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
|
||||
* 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
|
||||
* @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
|
||||
*/
|
||||
public static function excerpt($file, $line, $context = 2) {
|
||||
$lines = array();
|
||||
if (!file_exists($file)) {
|
||||
return array();
|
||||
}
|
||||
$data = file_get_contents($file);
|
||||
if (empty($data)) {
|
||||
return $lines;
|
||||
}
|
||||
if (strpos($data, "\n") !== false) {
|
||||
$data = explode("\n", $data);
|
||||
}
|
||||
if (!isset($data[$line])) {
|
||||
return $lines;
|
||||
}
|
||||
for ($i = $line - ($context + 1); $i < $line + $context; $i++) {
|
||||
if (!isset($data[$i])) {
|
||||
continue;
|
||||
}
|
||||
$string = str_replace(array("\r\n", "\n"), "", self::_highlight($data[$i]));
|
||||
if ($i == $line) {
|
||||
$lines[] = '<span class="code-highlight">' . $string . '</span>';
|
||||
} else {
|
||||
$lines[] = $string;
|
||||
}
|
||||
}
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the highlight_string funciton 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
|
||||
* @return string
|
||||
*/
|
||||
protected static function _highlight($str) {
|
||||
if (function_exists('hphp_log') || function_exists('hphp_gettid')) {
|
||||
return htmlentities($str);
|
||||
}
|
||||
$added = false;
|
||||
if (strpos($str, '<?php') === false) {
|
||||
$added = true;
|
||||
$str = "<?php \n" . $str;
|
||||
}
|
||||
$highlight = highlight_string($str, true);
|
||||
if ($added) {
|
||||
$highlight = str_replace(
|
||||
'<?php <br />',
|
||||
'',
|
||||
$highlight
|
||||
);
|
||||
}
|
||||
return $highlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a variable to a string for debug output.
|
||||
*
|
||||
* *Note:* The following keys will have their contents
|
||||
* replaced with `*****`:
|
||||
*
|
||||
* - password
|
||||
* - login
|
||||
* - 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.
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return string The dumped variable.
|
||||
*/
|
||||
protected static function _export($var, $depth, $indent) {
|
||||
switch (self::getType($var)) {
|
||||
case 'boolean':
|
||||
return ($var) ? 'true' : 'false';
|
||||
case 'integer':
|
||||
return '(int) ' . $var;
|
||||
case 'float':
|
||||
return '(float) ' . $var;
|
||||
case 'string':
|
||||
if (trim($var) == '') {
|
||||
return "''";
|
||||
}
|
||||
return "'" . $var . "'";
|
||||
case 'array':
|
||||
return self::_array($var, $depth - 1, $indent + 1);
|
||||
case 'resource':
|
||||
return strtolower(gettype($var));
|
||||
case 'null':
|
||||
return 'null';
|
||||
default:
|
||||
return self::_object($var, $depth - 1, $indent + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export an array type object. Filters out keys used in datasource configuration.
|
||||
*
|
||||
* The following keys are replaced with ***'s
|
||||
*
|
||||
* - password
|
||||
* - login
|
||||
* - 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.
|
||||
* @return string Exported array.
|
||||
*/
|
||||
protected static function _array(array $var, $depth, $indent) {
|
||||
$secrets = array(
|
||||
'password' => '*****',
|
||||
'login' => '*****',
|
||||
'host' => '*****',
|
||||
'database' => '*****',
|
||||
'port' => '*****',
|
||||
'prefix' => '*****',
|
||||
'schema' => '*****'
|
||||
);
|
||||
$replace = array_intersect_key($secrets, $var);
|
||||
$var = $replace + $var;
|
||||
|
||||
$out = "array(";
|
||||
$n = $break = $end = null;
|
||||
if (!empty($var)) {
|
||||
$n = "\n";
|
||||
$break = "\n" . str_repeat("\t", $indent);
|
||||
$end = "\n" . str_repeat("\t", $indent - 1);
|
||||
}
|
||||
$vars = array();
|
||||
|
||||
if ($depth >= 0) {
|
||||
foreach ($var as $key => $val) {
|
||||
// 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);
|
||||
}
|
||||
$vars[] = $break . self::exportVar($key) .
|
||||
' => ' .
|
||||
$val;
|
||||
}
|
||||
} else {
|
||||
$vars[] = $break . '[maximum depth reached]';
|
||||
}
|
||||
return $out . implode(',', $vars) . $end . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return string
|
||||
* @see Debugger::exportVar()
|
||||
*/
|
||||
protected static function _object($var, $depth, $indent) {
|
||||
$out = '';
|
||||
$props = array();
|
||||
|
||||
$className = get_class($var);
|
||||
$out .= 'object(' . $className . ') {';
|
||||
|
||||
if ($depth > 0) {
|
||||
$end = "\n" . str_repeat("\t", $indent - 1);
|
||||
$break = "\n" . str_repeat("\t", $indent);
|
||||
$objectVars = get_object_vars($var);
|
||||
foreach ($objectVars as $key => $value) {
|
||||
$value = self::_export($value, $depth - 1, $indent);
|
||||
$props[] = "$key => " . $value;
|
||||
}
|
||||
$out .= $break . implode($break, $props) . $end;
|
||||
}
|
||||
$out .= '}';
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/Set the output format for Debugger error rendering.
|
||||
*
|
||||
* @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.
|
||||
* @throws CakeException when choosing a format that doesn't exist.
|
||||
*/
|
||||
public static function outputAs($format = null) {
|
||||
$self = Debugger::getInstance();
|
||||
if ($format === null) {
|
||||
return $self->_outputFormat;
|
||||
}
|
||||
if ($format !== false && !isset($self->_templates[$format])) {
|
||||
throw new CakeException(__d('cake_dev', 'Invalid Debugger output format.'));
|
||||
}
|
||||
$self->_outputFormat = $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an output format or update a format in 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.
|
||||
* An error formatter can have the following keys:
|
||||
*
|
||||
* - 'error' - Used for the container for the error message. Gets the following template
|
||||
* variables: `id`, `error`, `code`, `description`, `path`, `line`, `links`, `info`
|
||||
* - 'info' - A combination of `code`, `context` and `trace`. Will be set with
|
||||
* the contents of the other template keys.
|
||||
* - 'trace' - The container for a stack trace. Gets the following template
|
||||
* variables: `trace`
|
||||
* - 'context' - The container element for the context variables.
|
||||
* 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
|
||||
* example.
|
||||
* - 'traceLine' - Used for creating lines in the stacktrace. Gets the following
|
||||
* template variables: `reference`, `path`, `line`
|
||||
*
|
||||
* Alternatively if you want to use a custom callback to do all the formatting, you can use
|
||||
* the callback key, and provide a callable:
|
||||
*
|
||||
* `Debugger::addFormat('custom', array('callback' => array($foo, 'outputError'));`
|
||||
*
|
||||
* 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`
|
||||
* 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
|
||||
* straight HTML output, or 'txt' for unformatted text.
|
||||
* @param array $strings Template strings, or a callback to be used for the output format.
|
||||
* @return The resulting format string set.
|
||||
*/
|
||||
public static function addFormat($format, array $strings) {
|
||||
$self = Debugger::getInstance();
|
||||
if (isset($self->_templates[$format])) {
|
||||
if (isset($strings['links'])) {
|
||||
$self->_templates[$format]['links'] = array_merge(
|
||||
$self->_templates[$format]['links'],
|
||||
$strings['links']
|
||||
);
|
||||
unset($strings['links']);
|
||||
}
|
||||
$self->_templates[$format] = array_merge($self->_templates[$format], $strings);
|
||||
} else {
|
||||
$self->_templates[$format] = $strings;
|
||||
}
|
||||
return $self->_templates[$format];
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches output format, updates format strings.
|
||||
* Can be used to switch the active output format:
|
||||
*
|
||||
* @param string $format Format to use, including 'js' for JavaScript-enhanced HTML, 'html' for
|
||||
* 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
|
||||
* in 3.0
|
||||
*/
|
||||
public function output($format = null, $strings = array()) {
|
||||
$self = Debugger::getInstance();
|
||||
$data = null;
|
||||
|
||||
if (is_null($format)) {
|
||||
return Debugger::outputAs();
|
||||
}
|
||||
|
||||
if (!empty($strings)) {
|
||||
return Debugger::addFormat($format, $strings);
|
||||
}
|
||||
|
||||
if ($format === true && !empty($self->_data)) {
|
||||
$data = $self->_data;
|
||||
$self->_data = array();
|
||||
$format = false;
|
||||
}
|
||||
Debugger::outputAs($format);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a processed array of data from an error and displays it in the chosen format.
|
||||
*
|
||||
* @param string $data
|
||||
* @return void
|
||||
*/
|
||||
public function outputError($data) {
|
||||
$defaults = array(
|
||||
'level' => 0,
|
||||
'error' => 0,
|
||||
'code' => 0,
|
||||
'description' => '',
|
||||
'file' => '',
|
||||
'line' => 0,
|
||||
'context' => array(),
|
||||
'start' => 2,
|
||||
);
|
||||
$data += $defaults;
|
||||
|
||||
$files = $this->trace(array('start' => $data['start'], 'format' => 'points'));
|
||||
$code = '';
|
||||
$file = null;
|
||||
if (isset($files[0]['file'])) {
|
||||
$file = $files[0];
|
||||
} elseif (isset($files[1]['file'])) {
|
||||
$file = $files[1];
|
||||
}
|
||||
if ($file) {
|
||||
$code = $this->excerpt($file['file'], $file['line'] - 1, 1);
|
||||
}
|
||||
$trace = $this->trace(array('start' => $data['start'], 'depth' => '20'));
|
||||
$insertOpts = array('before' => '{:', 'after' => '}');
|
||||
$context = array();
|
||||
$links = array();
|
||||
$info = '';
|
||||
|
||||
foreach ((array)$data['context'] as $var => $value) {
|
||||
$context[] = "\${$var} = " . $this->exportVar($value, 3);
|
||||
}
|
||||
|
||||
switch ($this->_outputFormat) {
|
||||
case false:
|
||||
$this->_data[] = compact('context', 'trace') + $data;
|
||||
return;
|
||||
case 'log':
|
||||
$this->log(compact('context', 'trace') + $data);
|
||||
return;
|
||||
}
|
||||
|
||||
$data['trace'] = $trace;
|
||||
$data['id'] = 'cakeErr' . uniqid();
|
||||
$tpl = array_merge($this->_templates['base'], $this->_templates[$this->_outputFormat]);
|
||||
|
||||
if (isset($tpl['links'])) {
|
||||
foreach ($tpl['links'] as $key => $val) {
|
||||
$links[$key] = String::insert($val, $data, $insertOpts);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($tpl['escapeContext'])) {
|
||||
$context = h($context);
|
||||
}
|
||||
|
||||
$infoData = compact('code', 'context', 'trace');
|
||||
foreach ($infoData as $key => $value) {
|
||||
if (empty($value) || !isset($tpl[$key])) {
|
||||
continue;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
$value = join("\n", $value);
|
||||
}
|
||||
$info .= String::insert($tpl[$key], array($key => $value) + $data, $insertOpts);
|
||||
}
|
||||
$links = join(' ', $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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of the given variable. Will return the classname
|
||||
* for objects.
|
||||
*
|
||||
* @param mixed $var The variable to get the type of
|
||||
* @return string The type of variable.
|
||||
*/
|
||||
public static function getType($var) {
|
||||
if (is_object($var)) {
|
||||
return get_class($var);
|
||||
}
|
||||
if (is_null($var)) {
|
||||
return 'null';
|
||||
}
|
||||
if (is_string($var)) {
|
||||
return 'string';
|
||||
}
|
||||
if (is_array($var)) {
|
||||
return 'array';
|
||||
}
|
||||
if (is_int($var)) {
|
||||
return 'integer';
|
||||
}
|
||||
if (is_bool($var)) {
|
||||
return 'boolean';
|
||||
}
|
||||
if (is_float($var)) {
|
||||
return 'float';
|
||||
}
|
||||
if (is_resource($var)) {
|
||||
return 'resource';
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the application's salt and cipher seed value has been changed from the default value.
|
||||
*
|
||||
* @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.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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
568
lib/Cake/Utility/File.php
Normal file
568
lib/Cake/Utility/File.php
Normal file
|
|
@ -0,0 +1,568 @@
|
|||
<?php
|
||||
/**
|
||||
* 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)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright 2005-2012, 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)
|
||||
*/
|
||||
|
||||
App::uses('Folder', 'Utility');
|
||||
|
||||
/**
|
||||
* Convenience class for reading, writing and appending to files.
|
||||
*
|
||||
* @package Cake.Utility
|
||||
*/
|
||||
class File {
|
||||
|
||||
/**
|
||||
* Folder object of the File
|
||||
*
|
||||
* @var Folder
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::$Folder
|
||||
*/
|
||||
public $Folder = null;
|
||||
|
||||
/**
|
||||
* Filename
|
||||
*
|
||||
* @var string
|
||||
* http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::$name
|
||||
*/
|
||||
public $name = null;
|
||||
|
||||
/**
|
||||
* File info
|
||||
*
|
||||
* @var array
|
||||
* http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::$info
|
||||
*/
|
||||
public $info = array();
|
||||
|
||||
/**
|
||||
* Holds the file handler resource if the file is opened
|
||||
*
|
||||
* @var resource
|
||||
* http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::$handle
|
||||
*/
|
||||
public $handle = null;
|
||||
|
||||
/**
|
||||
* Enable locking for file reading and writing
|
||||
*
|
||||
* @var boolean
|
||||
* http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::$lock
|
||||
*/
|
||||
public $lock = null;
|
||||
|
||||
/**
|
||||
* Path property
|
||||
*
|
||||
* Current file's absolute path
|
||||
*
|
||||
* @var mixed null
|
||||
* http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::$path
|
||||
*/
|
||||
public $path = null;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File
|
||||
*/
|
||||
public function __construct($path, $create = false, $mode = 0755) {
|
||||
$this->Folder = new Folder(dirname($path), $create, $mode);
|
||||
if (!is_dir($path)) {
|
||||
$this->name = basename($path);
|
||||
}
|
||||
$this->pwd();
|
||||
$create && !$this->exists() && $this->safe($path) && $this->create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the current file if it is opened
|
||||
*
|
||||
*/
|
||||
public function __destruct() {
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the File.
|
||||
*
|
||||
* @return boolean Success
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::create
|
||||
*/
|
||||
public function create() {
|
||||
$dir = $this->Folder->pwd();
|
||||
if (is_dir($dir) && is_writable($dir) && !$this->exists()) {
|
||||
if (touch($this->path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
$this->handle = fopen($this->path, $mode);
|
||||
if (is_resource($this->handle)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return mixed string on success, false on failure
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::read
|
||||
*/
|
||||
public function read($bytes = false, $mode = 'rb', $force = false) {
|
||||
if ($bytes === false && $this->lock === null) {
|
||||
return file_get_contents($this->path);
|
||||
}
|
||||
if ($this->open($mode, $force) === false) {
|
||||
return false;
|
||||
}
|
||||
if ($this->lock !== null && flock($this->handle, LOCK_SH) === false) {
|
||||
return false;
|
||||
}
|
||||
if (is_int($bytes)) {
|
||||
return fread($this->handle, $bytes);
|
||||
}
|
||||
|
||||
$data = '';
|
||||
while (!feof($this->handle)) {
|
||||
$data .= fgets($this->handle, 4096);
|
||||
}
|
||||
|
||||
if ($this->lock !== null) {
|
||||
flock($this->handle, LOCK_UN);
|
||||
}
|
||||
if ($bytes === false) {
|
||||
$this->close();
|
||||
}
|
||||
return trim($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @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
|
||||
*/
|
||||
public function offset($offset = false, $seek = SEEK_SET) {
|
||||
if ($offset === false) {
|
||||
if (is_resource($this->handle)) {
|
||||
return ftell($this->handle);
|
||||
}
|
||||
} elseif ($this->open() === true) {
|
||||
return fseek($this->handle, $offset, $seek) === 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a 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
|
||||
* @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) {
|
||||
$lineBreak = "\r\n";
|
||||
}
|
||||
return strtr($data, array("\r\n" => $lineBreak, "\n" => $lineBreak, "\r" => $lineBreak));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::write
|
||||
*/
|
||||
public function write($data, $mode = 'w', $force = false) {
|
||||
$success = false;
|
||||
if ($this->open($mode, $force) === true) {
|
||||
if ($this->lock !== null) {
|
||||
if (flock($this->handle, LOCK_EX) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (fwrite($this->handle, $data) !== false) {
|
||||
$success = true;
|
||||
}
|
||||
if ($this->lock !== null) {
|
||||
flock($this->handle, LOCK_UN);
|
||||
}
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append given data string to this File.
|
||||
*
|
||||
* @param string $data Data to write
|
||||
* @param string $force force the file to open
|
||||
* @return boolean Success
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::append
|
||||
*/
|
||||
public function append($data, $force = false) {
|
||||
return $this->write($data, 'a', $force);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the current file if it is opened.
|
||||
*
|
||||
* @return boolean 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() {
|
||||
if (!is_resource($this->handle)) {
|
||||
return true;
|
||||
}
|
||||
return fclose($this->handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the File.
|
||||
*
|
||||
* @return boolean 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;
|
||||
}
|
||||
if ($this->exists()) {
|
||||
return unlink($this->path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the File info as an array with the following keys:
|
||||
*
|
||||
* - dirname
|
||||
* - basename
|
||||
* - extension
|
||||
* - filename
|
||||
* - filesize
|
||||
* - mime
|
||||
*
|
||||
* @return array File information.
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::info
|
||||
*/
|
||||
public function info() {
|
||||
if ($this->info == null) {
|
||||
$this->info = pathinfo($this->path);
|
||||
}
|
||||
if (!isset($this->info['filename'])) {
|
||||
$this->info['filename'] = $this->name();
|
||||
}
|
||||
if (!isset($this->info['filesize'])) {
|
||||
$this->info['filesize'] = $this->size();
|
||||
}
|
||||
if (!isset($this->info['mime'])) {
|
||||
$this->info['mime'] = $this->mime();
|
||||
}
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 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) {
|
||||
$this->info();
|
||||
}
|
||||
if (isset($this->info['extension'])) {
|
||||
return $this->info['extension'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 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) {
|
||||
$this->info();
|
||||
}
|
||||
if (isset($this->info['extension'])) {
|
||||
return basename($this->name, '.' . $this->info['extension']);
|
||||
} elseif ($this->name) {
|
||||
return $this->name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* makes filename 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
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::safe
|
||||
*/
|
||||
public function safe($name = null, $ext = null) {
|
||||
if (!$name) {
|
||||
$name = $this->name;
|
||||
}
|
||||
if (!$ext) {
|
||||
$ext = $this->ext();
|
||||
}
|
||||
return preg_replace("/(?:[^\w\.-]+)/", "_", basename($name, $ext));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()}
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::md5
|
||||
*/
|
||||
public function md5($maxsize = 5) {
|
||||
if ($maxsize === true) {
|
||||
return md5_file($this->path);
|
||||
}
|
||||
|
||||
$size = $this->size();
|
||||
if ($size && $size < ($maxsize * 1024) * 1024) {
|
||||
return md5_file($this->path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path of the File.
|
||||
*
|
||||
* @return string Full path to 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;
|
||||
}
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the File exists.
|
||||
*
|
||||
* @return boolean 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() {
|
||||
return (file_exists($this->path) && is_file($this->path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "chmod" (permissions) of the File.
|
||||
*
|
||||
* @return string Permissions for the file
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::perms
|
||||
*/
|
||||
public function perms() {
|
||||
if ($this->exists()) {
|
||||
return substr(sprintf('%o', fileperms($this->path)), -4);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Filesize
|
||||
*
|
||||
* @return integer 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() {
|
||||
if ($this->exists()) {
|
||||
return filesize($this->path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the File is writable.
|
||||
*
|
||||
* @return boolean true if its writable, false otherwise
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::writable
|
||||
*/
|
||||
public function writable() {
|
||||
return is_writable($this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the File is executable.
|
||||
*
|
||||
* @return boolean true if its executable, false otherwise
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::executable
|
||||
*/
|
||||
public function executable() {
|
||||
return is_executable($this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the File is readable.
|
||||
*
|
||||
* @return boolean 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() {
|
||||
return is_readable($this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the File's owner.
|
||||
*
|
||||
* @return integer the Fileowner
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::owner
|
||||
*/
|
||||
public function owner() {
|
||||
if ($this->exists()) {
|
||||
return fileowner($this->path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the File's group.
|
||||
*
|
||||
* @return integer the Filegroup
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::group
|
||||
*/
|
||||
public function group() {
|
||||
if ($this->exists()) {
|
||||
return filegroup($this->path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns last access time.
|
||||
*
|
||||
* @return integer timestamp Timestamp of last access time
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::lastAccess
|
||||
*/
|
||||
public function lastAccess() {
|
||||
if ($this->exists()) {
|
||||
return fileatime($this->path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns last modified time.
|
||||
*
|
||||
* @return integer timestamp Timestamp of last modification
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::lastChange
|
||||
*/
|
||||
public function lastChange() {
|
||||
if ($this->exists()) {
|
||||
return filemtime($this->path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current folder.
|
||||
*
|
||||
* @return Folder Current folder
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::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
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#File::copy
|
||||
*/
|
||||
public function copy($dest, $overwrite = true) {
|
||||
if (!$this->exists() || is_file($dest) && !$overwrite) {
|
||||
return false;
|
||||
}
|
||||
return copy($this->path, $dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function mime() {
|
||||
if (!$this->exists()) {
|
||||
return false;
|
||||
}
|
||||
if (function_exists('finfo_open')) {
|
||||
$finfo = finfo_open(FILEINFO_MIME);
|
||||
list($type, $charset) = explode(';', finfo_file($finfo, $this->pwd()));
|
||||
return $type;
|
||||
} elseif (function_exists('mime_content_type')) {
|
||||
return mime_content_type($this->pwd());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
792
lib/Cake/Utility/Folder.php
Normal file
792
lib/Cake/Utility/Folder.php
Normal file
|
|
@ -0,0 +1,792 @@
|
|||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright 2005-2012, 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)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Folder structure browser, lists folders and files.
|
||||
* Provides an Object interface for Common directory related tasks.
|
||||
*
|
||||
* @package Cake.Utility
|
||||
*/
|
||||
class Folder {
|
||||
|
||||
/**
|
||||
* Path to Folder.
|
||||
*
|
||||
* @var string
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::$path
|
||||
*/
|
||||
public $path = null;
|
||||
|
||||
/**
|
||||
* Sortedness. Whether or not list results
|
||||
* should be sorted by name.
|
||||
*
|
||||
* @var boolean
|
||||
* @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.
|
||||
*
|
||||
* @var integer
|
||||
* http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::$mode
|
||||
*/
|
||||
public $mode = 0755;
|
||||
|
||||
/**
|
||||
* Holds messages from last method.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_messages = array();
|
||||
|
||||
/**
|
||||
* Holds errors from last method.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_errors = array();
|
||||
|
||||
/**
|
||||
* Holds array of complete directory paths.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_directories;
|
||||
|
||||
/**
|
||||
* Holds array of complete file paths.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_files;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder
|
||||
*/
|
||||
public function __construct($path = false, $create = false, $mode = false) {
|
||||
if (empty($path)) {
|
||||
$path = TMP;
|
||||
}
|
||||
if ($mode) {
|
||||
$this->mode = $mode;
|
||||
}
|
||||
|
||||
if (!file_exists($path) && $create === true) {
|
||||
$this->create($path, $this->mode);
|
||||
}
|
||||
if (!Folder::isAbsolute($path)) {
|
||||
$path = realpath($path);
|
||||
}
|
||||
if (!empty($path)) {
|
||||
$this->cd($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current path.
|
||||
*
|
||||
* @return string Current path
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::pwd
|
||||
*/
|
||||
public function pwd() {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change directory to $path.
|
||||
*
|
||||
* @param string $path Path to the directory to change to
|
||||
* @return string The new path. Returns false on failure
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::cd
|
||||
*/
|
||||
public function cd($path) {
|
||||
$path = $this->realpath($path);
|
||||
if (is_dir($path)) {
|
||||
return $this->path = $path;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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
|
||||
* @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) {
|
||||
$dirs = $files = array();
|
||||
|
||||
if (!$this->pwd()) {
|
||||
return array($dirs, $files);
|
||||
}
|
||||
if (is_array($exceptions)) {
|
||||
$exceptions = array_flip($exceptions);
|
||||
}
|
||||
$skipHidden = isset($exceptions['.']) || $exceptions === true;
|
||||
|
||||
try {
|
||||
$iterator = new DirectoryIterator($this->path);
|
||||
} catch (Exception $e) {
|
||||
return array($dirs, $files);
|
||||
}
|
||||
|
||||
foreach ($iterator as $item) {
|
||||
if ($item->isDot()) {
|
||||
continue;
|
||||
}
|
||||
$name = $item->getFileName();
|
||||
if ($skipHidden && $name[0] === '.' || isset($exceptions[$name])) {
|
||||
continue;
|
||||
}
|
||||
if ($fullPath) {
|
||||
$name = $item->getPathName();
|
||||
}
|
||||
if ($item->isDir()) {
|
||||
$dirs[] = $name;
|
||||
} else {
|
||||
$files[] = $name;
|
||||
}
|
||||
}
|
||||
if ($sort || $this->sort) {
|
||||
sort($dirs);
|
||||
sort($files);
|
||||
}
|
||||
return array($dirs, $files);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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);
|
||||
return array_values(preg_grep('/^' . $regexpPattern . '$/i', $files));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return array Files matching $pattern
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::findRecursive
|
||||
*/
|
||||
public function findRecursive($pattern = '.*', $sort = false) {
|
||||
if (!$this->pwd()) {
|
||||
return array();
|
||||
}
|
||||
$startsOn = $this->path;
|
||||
$out = $this->_findRecursive($pattern, $sort);
|
||||
$this->cd($startsOn);
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private helper function for findRecursive.
|
||||
*
|
||||
* @param string $pattern Pattern to match against
|
||||
* @param boolean $sort Whether results should be sorted.
|
||||
* @return array Files matching pattern
|
||||
*/
|
||||
protected function _findRecursive($pattern, $sort = false) {
|
||||
list($dirs, $files) = $this->read($sort);
|
||||
$found = array();
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (preg_match('/^' . $pattern . '$/i', $file)) {
|
||||
$found[] = Folder::addPathElement($this->path, $file);
|
||||
}
|
||||
}
|
||||
$start = $this->path;
|
||||
|
||||
foreach ($dirs as $dir) {
|
||||
$this->cd(Folder::addPathElement($start, $dir));
|
||||
$found = array_merge($found, $this->findRecursive($pattern, $sort));
|
||||
}
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if given $path is a Windows path.
|
||||
*
|
||||
* @param string $path Path to check
|
||||
* @return boolean 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) == '\\\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if given $path is an absolute path.
|
||||
*
|
||||
* @param string $path Path to check
|
||||
* @return boolean 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) == '\\\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.)
|
||||
*
|
||||
* @param string $path Path to check
|
||||
* @return string Set of slashes ("\\" or "/")
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::normalizePath
|
||||
*/
|
||||
public static function normalizePath($path) {
|
||||
return Folder::correctSlashFor($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.)
|
||||
*
|
||||
* @param string $path Path to check
|
||||
* @return string Set of slashes ("\\" or "/")
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::correctSlashFor
|
||||
*/
|
||||
public static function correctSlashFor($path) {
|
||||
return (Folder::isWindowsPath($path)) ? '\\' : '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns $path with added terminating slash (corrected for Windows or other OS).
|
||||
*
|
||||
* @param string $path Path to check
|
||||
* @return string Path with ending slash
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::slashTerm
|
||||
*/
|
||||
public static function slashTerm($path) {
|
||||
if (Folder::isSlashTerm($path)) {
|
||||
return $path;
|
||||
}
|
||||
return $path . Folder::correctSlashFor($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns $path with $element added, with correct slash in-between.
|
||||
*
|
||||
* @param string $path Path
|
||||
* @param string $element Element to and 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the File is in a given CakePath.
|
||||
*
|
||||
* @param string $path The path to check.
|
||||
* @return boolean
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::inCakePath
|
||||
*/
|
||||
public function inCakePath($path = '') {
|
||||
$dir = substr(Folder::slashTerm(ROOT), 0, -1);
|
||||
$newdir = $dir . $path;
|
||||
|
||||
return $this->inPath($newdir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the File is in 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
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::inPath
|
||||
*/
|
||||
public function inPath($path = '', $reverse = false) {
|
||||
$dir = Folder::slashTerm($path);
|
||||
$current = Folder::slashTerm($this->pwd());
|
||||
|
||||
if (!$reverse) {
|
||||
$return = preg_match('/^(.*)' . preg_quote($dir, '/') . '(.*)/', $current);
|
||||
} else {
|
||||
$return = preg_match('/^(.*)' . preg_quote($current, '/') . '(.*)/', $dir);
|
||||
}
|
||||
return (bool)$return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @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()) {
|
||||
if (!$mode) {
|
||||
$mode = $this->mode;
|
||||
}
|
||||
|
||||
if ($recursive === false && is_dir($path)) {
|
||||
//@codingStandardsIgnoreStart
|
||||
if (@chmod($path, intval($mode, 8))) {
|
||||
//@codingStandardsIgnoreEnd
|
||||
$this->_messages[] = __d('cake_dev', '%s changed to %s', $path, $mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->_errors[] = __d('cake_dev', '%s NOT changed to %s', $path, $mode);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_dir($path)) {
|
||||
$paths = $this->tree($path);
|
||||
|
||||
foreach ($paths as $type) {
|
||||
foreach ($type as $fullpath) {
|
||||
$check = explode(DS, $fullpath);
|
||||
$count = count($check);
|
||||
|
||||
if (in_array($check[$count - 1], $exceptions)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//@codingStandardsIgnoreStart
|
||||
if (@chmod($fullpath, intval($mode, 8))) {
|
||||
//@codingStandardsIgnoreEnd
|
||||
$this->_messages[] = __d('cake_dev', '%s changed to %s', $fullpath, $mode);
|
||||
} else {
|
||||
$this->_errors[] = __d('cake_dev', '%s NOT changed to %s', $fullpath, $mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->_errors)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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) {
|
||||
$path = $this->path;
|
||||
}
|
||||
$files = array();
|
||||
$directories = array($path);
|
||||
|
||||
if (is_array($exceptions)) {
|
||||
$exceptions = array_flip($exceptions);
|
||||
}
|
||||
$skipHidden = false;
|
||||
if ($exceptions === true) {
|
||||
$skipHidden = true;
|
||||
} elseif (isset($exceptions['.'])) {
|
||||
$skipHidden = true;
|
||||
unset($exceptions['.']);
|
||||
}
|
||||
|
||||
try {
|
||||
$directory = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::KEY_AS_PATHNAME | RecursiveDirectoryIterator::CURRENT_AS_SELF);
|
||||
$iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
|
||||
} catch (Exception $e) {
|
||||
if ($type === null) {
|
||||
return array(array(), array());
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
foreach ($iterator as $itemPath => $fsIterator) {
|
||||
if ($skipHidden) {
|
||||
$subPathName = $fsIterator->getSubPathname();
|
||||
if ($subPathName{0} == '.' || strpos($subPathName, DS . '.') !== false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$item = $fsIterator->current();
|
||||
if (!empty($exceptions) && isset($exceptions[$item->getFilename()])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($item->isFile()) {
|
||||
$files[] = $itemPath;
|
||||
} elseif ($item->isDir() && !$item->isDot()) {
|
||||
$directories[] = $itemPath;
|
||||
}
|
||||
}
|
||||
if ($type === null) {
|
||||
return array($directories, $files);
|
||||
}
|
||||
if ($type === 'dir') {
|
||||
return $directories;
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a directory structure recursively. Can be used to create
|
||||
* deep path structures like `/foo/bar/baz/shoe/horn`
|
||||
*
|
||||
* @param string $pathname The directory structure to create
|
||||
* @param integer $mode octal value 0755
|
||||
* @return boolean 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) {
|
||||
if (is_dir($pathname) || empty($pathname)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$mode) {
|
||||
$mode = $this->mode;
|
||||
}
|
||||
|
||||
if (is_file($pathname)) {
|
||||
$this->_errors[] = __d('cake_dev', '%s is a file', $pathname);
|
||||
return false;
|
||||
}
|
||||
$pathname = rtrim($pathname, DS);
|
||||
$nextPathname = substr($pathname, 0, strrpos($pathname, DS));
|
||||
|
||||
if ($this->create($nextPathname, $mode)) {
|
||||
if (!file_exists($pathname)) {
|
||||
$old = umask(0);
|
||||
if (mkdir($pathname, $mode)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size in bytes of this Folder and its contents.
|
||||
*
|
||||
* @return integer 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() {
|
||||
$size = 0;
|
||||
$directory = Folder::slashTerm($this->path);
|
||||
$stack = array($directory);
|
||||
$count = count($stack);
|
||||
for ($i = 0, $j = $count; $i < $j; ++$i) {
|
||||
if (is_file($stack[$i])) {
|
||||
$size += filesize($stack[$i]);
|
||||
} elseif (is_dir($stack[$i])) {
|
||||
$dir = dir($stack[$i]);
|
||||
if ($dir) {
|
||||
while (false !== ($entry = $dir->read())) {
|
||||
if ($entry === '.' || $entry === '..') {
|
||||
continue;
|
||||
}
|
||||
$add = $stack[$i] . $entry;
|
||||
|
||||
if (is_dir($stack[$i] . $entry)) {
|
||||
$add = Folder::slashTerm($add);
|
||||
}
|
||||
$stack[] = $add;
|
||||
}
|
||||
$dir->close();
|
||||
}
|
||||
}
|
||||
$j = count($stack);
|
||||
}
|
||||
return $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively Remove directories if the system allows.
|
||||
*
|
||||
* @param string $path Path of directory to delete
|
||||
* @return boolean Success
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::delete
|
||||
*/
|
||||
public function delete($path = null) {
|
||||
if (!$path) {
|
||||
$path = $this->pwd();
|
||||
}
|
||||
if (!$path) {
|
||||
return null;
|
||||
}
|
||||
$path = Folder::slashTerm($path);
|
||||
if (is_dir($path)) {
|
||||
try {
|
||||
$directory = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::CURRENT_AS_SELF);
|
||||
$iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::CHILD_FIRST);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($iterator as $item) {
|
||||
$filePath = $item->getPathname();
|
||||
if ($item->isFile() || $item->isLink()) {
|
||||
//@codingStandardsIgnoreStart
|
||||
if (@unlink($filePath)) {
|
||||
//@codingStandardsIgnoreEnd
|
||||
$this->_messages[] = __d('cake_dev', '%s removed', $filePath);
|
||||
} else {
|
||||
$this->_errors[] = __d('cake_dev', '%s NOT removed', $filePath);
|
||||
}
|
||||
} elseif ($item->isDir() && !$item->isDot()) {
|
||||
//@codingStandardsIgnoreStart
|
||||
if (@rmdir($filePath)) {
|
||||
//@codingStandardsIgnoreEnd
|
||||
$this->_messages[] = __d('cake_dev', '%s removed', $filePath);
|
||||
} else {
|
||||
$this->_errors[] = __d('cake_dev', '%s NOT removed', $filePath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$path = rtrim($path, DS);
|
||||
//@codingStandardsIgnoreStart
|
||||
if (@rmdir($path)) {
|
||||
//@codingStandardsIgnoreEnd
|
||||
$this->_messages[] = __d('cake_dev', '%s removed', $path);
|
||||
} else {
|
||||
$this->_errors[] = __d('cake_dev', '%s NOT removed', $path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive directory copy.
|
||||
*
|
||||
* ### Options
|
||||
*
|
||||
* - `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.
|
||||
* - `skip` Files/directories to skip.
|
||||
*
|
||||
* @param array|string $options Either an array of options (see above) or a string of the destination directory.
|
||||
* @return boolean Success
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::copy
|
||||
*/
|
||||
public function copy($options = array()) {
|
||||
if (!$this->pwd()) {
|
||||
return false;
|
||||
}
|
||||
$to = null;
|
||||
if (is_string($options)) {
|
||||
$to = $options;
|
||||
$options = array();
|
||||
}
|
||||
$options = array_merge(array('to' => $to, 'from' => $this->path, 'mode' => $this->mode, 'skip' => array()), $options);
|
||||
|
||||
$fromDir = $options['from'];
|
||||
$toDir = $options['to'];
|
||||
$mode = $options['mode'];
|
||||
|
||||
if (!$this->cd($fromDir)) {
|
||||
$this->_errors[] = __d('cake_dev', '%s not found', $fromDir);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_dir($toDir)) {
|
||||
$this->create($toDir, $mode);
|
||||
}
|
||||
|
||||
if (!is_writable($toDir)) {
|
||||
$this->_errors[] = __d('cake_dev', '%s not writable', $toDir);
|
||||
return false;
|
||||
}
|
||||
|
||||
$exceptions = array_merge(array('.', '..', '.svn'), $options['skip']);
|
||||
//@codingStandardsIgnoreStart
|
||||
if ($handle = @opendir($fromDir)) {
|
||||
//@codingStandardsIgnoreEnd
|
||||
while (false !== ($item = readdir($handle))) {
|
||||
if (!in_array($item, $exceptions)) {
|
||||
$from = Folder::addPathElement($fromDir, $item);
|
||||
$to = Folder::addPathElement($toDir, $item);
|
||||
if (is_file($from)) {
|
||||
if (copy($from, $to)) {
|
||||
chmod($to, intval($mode, 8));
|
||||
touch($to, filemtime($from));
|
||||
$this->_messages[] = __d('cake_dev', '%s copied to %s', $from, $to);
|
||||
} else {
|
||||
$this->_errors[] = __d('cake_dev', '%s NOT copied to %s', $from, $to);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_dir($from) && !file_exists($to)) {
|
||||
$old = umask(0);
|
||||
if (mkdir($to, $mode)) {
|
||||
umask($old);
|
||||
$old = umask(0);
|
||||
chmod($to, $mode);
|
||||
umask($old);
|
||||
$this->_messages[] = __d('cake_dev', '%s created', $to);
|
||||
$options = array_merge($options, array('to' => $to, 'from' => $from));
|
||||
$this->copy($options);
|
||||
} else {
|
||||
$this->_errors[] = __d('cake_dev', '%s not created', $to);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($this->_errors)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive directory move.
|
||||
*
|
||||
* ### Options
|
||||
*
|
||||
* - `to` The directory to copy to.
|
||||
* - `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.
|
||||
*
|
||||
* @param array $options (to, from, chmod, skip)
|
||||
* @return boolean Success
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::move
|
||||
*/
|
||||
public function move($options) {
|
||||
$to = null;
|
||||
if (is_string($options)) {
|
||||
$to = $options;
|
||||
$options = (array)$options;
|
||||
}
|
||||
$options = array_merge(
|
||||
array('to' => $to, 'from' => $this->path, 'mode' => $this->mode, 'skip' => array()),
|
||||
$options
|
||||
);
|
||||
|
||||
if ($this->copy($options)) {
|
||||
if ($this->delete($options['from'])) {
|
||||
return (bool)$this->cd($options['to']);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* get messages from latest method
|
||||
*
|
||||
* @return array
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::messages
|
||||
*/
|
||||
public function messages() {
|
||||
return $this->_messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* get error from latest method
|
||||
*
|
||||
* @return array
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::errors
|
||||
*/
|
||||
public function errors() {
|
||||
return $this->_errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the real path (taking ".." and such into account)
|
||||
*
|
||||
* @param string $path Path to resolve
|
||||
* @return string The resolved path
|
||||
* @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;
|
||||
}
|
||||
$parts = explode(DS, $path);
|
||||
$newparts = array();
|
||||
$newpath = '';
|
||||
if ($path[0] === DS) {
|
||||
$newpath = DS;
|
||||
}
|
||||
|
||||
while (($part = array_shift($parts)) !== null) {
|
||||
if ($part === '.' || $part === '') {
|
||||
continue;
|
||||
}
|
||||
if ($part === '..') {
|
||||
if (!empty($newparts)) {
|
||||
array_pop($newparts);
|
||||
continue;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$newparts[] = $part;
|
||||
}
|
||||
$newpath .= implode(DS, $newparts);
|
||||
|
||||
return Folder::slashTerm($newpath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::isSlashTerm
|
||||
*/
|
||||
public static function isSlashTerm($path) {
|
||||
$lastChar = $path[strlen($path) - 1];
|
||||
return $lastChar === '/' || $lastChar === '\\';
|
||||
}
|
||||
|
||||
}
|
||||
989
lib/Cake/Utility/Hash.php
Normal file
989
lib/Cake/Utility/Hash.php
Normal file
|
|
@ -0,0 +1,989 @@
|
|||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright 2005-2011, 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)
|
||||
*/
|
||||
|
||||
App::uses('String', '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
|
||||
* support for pseudo Xpath, its more fully featured dot notation provides
|
||||
* similar features in a more consistent implementation.
|
||||
*
|
||||
* @package Cake.Utility
|
||||
*/
|
||||
class Hash {
|
||||
|
||||
/**
|
||||
* Get a single value specified by $path out of $data.
|
||||
* Does not support the full dot notation feature set,
|
||||
* but is faster for simple read operations.
|
||||
*
|
||||
* @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.
|
||||
* @return mixed The value fetched from the array, or null.
|
||||
*/
|
||||
public static function get(array $data, $path) {
|
||||
if (empty($data) || empty($path)) {
|
||||
return null;
|
||||
}
|
||||
if (is_string($path)) {
|
||||
$parts = explode('.', $path);
|
||||
} else {
|
||||
$parts = $path;
|
||||
}
|
||||
foreach ($parts as $key) {
|
||||
if (is_array($data) && isset($data[$key])) {
|
||||
$data =& $data[$key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the values from an array matching the $path expression.
|
||||
* The path expression is a dot separated expression, that can contain a set
|
||||
* of patterns and expressions:
|
||||
*
|
||||
* - `{n}` Matches any numeric key, or integer.
|
||||
* - `{s}` Matches any string key.
|
||||
* - `Foo` Matches any key with the exact same value.
|
||||
*
|
||||
* There are a number of attribute operators:
|
||||
*
|
||||
* - `=`, `!=` Equality.
|
||||
* - `>`, `<`, `>=`, `<=` Value comparison.
|
||||
* - `=/.../` Regular expression pattern match.
|
||||
*
|
||||
* Given a set of User array data, from a `$User->find('all')` call:
|
||||
*
|
||||
* - `1.User.name` Get the name of the user at index 1.
|
||||
* - `{n}.User.name` Get the name of every user in the set of users.
|
||||
* - `{n}.User[id]` Get the name of every user with an id key.
|
||||
* - `{n}.User[id>=2]` Get the name of every user with an id key greater than or equal to 2.
|
||||
* - `{n}.User[username=/^paul/]` Get User elements with username matching `^paul`.
|
||||
*
|
||||
* @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
|
||||
* if there are no matches.
|
||||
*/
|
||||
public static function extract(array $data, $path) {
|
||||
if (empty($path)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
// Simple paths.
|
||||
if (!preg_match('/[{\[]/', $path)) {
|
||||
return (array)self::get($data, $path);
|
||||
}
|
||||
|
||||
if (strpos($path, '[') === false) {
|
||||
$tokens = explode('.', $path);
|
||||
} else {
|
||||
$tokens = String::tokenize($path, '.', '[', ']');
|
||||
}
|
||||
|
||||
$_key = '__set_item__';
|
||||
|
||||
$context = array($_key => array($data));
|
||||
|
||||
foreach ($tokens as $token) {
|
||||
$next = array();
|
||||
|
||||
$conditions = false;
|
||||
$position = strpos($token, '[');
|
||||
if ($position !== false) {
|
||||
$conditions = substr($token, $position);
|
||||
$token = substr($token, 0, $position);
|
||||
}
|
||||
|
||||
foreach ($context[$_key] as $item) {
|
||||
foreach ($item as $k => $v) {
|
||||
if (self::_matchToken($k, $token)) {
|
||||
$next[] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter for attributes.
|
||||
if ($conditions) {
|
||||
$filter = array();
|
||||
foreach ($next as $item) {
|
||||
if (self::_matches($item, $conditions)) {
|
||||
$filter[] = $item;
|
||||
}
|
||||
}
|
||||
$next = $filter;
|
||||
}
|
||||
$context = array($_key => $next);
|
||||
|
||||
}
|
||||
return $context[$_key];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected static function _matchToken($key, $token) {
|
||||
if ($token === '{n}') {
|
||||
return is_numeric($key);
|
||||
}
|
||||
if ($token === '{s}') {
|
||||
return is_string($key);
|
||||
}
|
||||
if (is_numeric($token)) {
|
||||
return ($key == $token);
|
||||
}
|
||||
return ($key === $token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not $data matches the attribute patterns
|
||||
*
|
||||
* @param array $data Array of data to match.
|
||||
* @param string $selector The patterns to match.
|
||||
* @return boolean Fitness of expression.
|
||||
*/
|
||||
protected static function _matches(array $data, $selector) {
|
||||
preg_match_all(
|
||||
'/(\[ (?<attr>[^=><!]+?) (\s* (?<op>[><!]?[=]|[><]) \s* (?<val>[^\]]+) )? \])/x',
|
||||
$selector,
|
||||
$conditions,
|
||||
PREG_SET_ORDER
|
||||
);
|
||||
|
||||
foreach ($conditions as $cond) {
|
||||
$attr = $cond['attr'];
|
||||
$op = isset($cond['op']) ? $cond['op'] : null;
|
||||
$val = isset($cond['val']) ? $cond['val'] : null;
|
||||
|
||||
// Presence test.
|
||||
if (empty($op) && empty($val) && !isset($data[$attr])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Empty attribute = fail.
|
||||
if (!(isset($data[$attr]) || array_key_exists($attr, $data))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$prop = isset($data[$attr]) ? $data[$attr] : null;
|
||||
|
||||
// Pattern matches and other operators.
|
||||
if ($op === '=' && $val && $val[0] === '/') {
|
||||
if (!preg_match($val, $prop)) {
|
||||
return false;
|
||||
}
|
||||
} elseif (
|
||||
($op === '=' && $prop != $val) ||
|
||||
($op === '!=' && $prop == $val) ||
|
||||
($op === '>' && $prop <= $val) ||
|
||||
($op === '<' && $prop >= $val) ||
|
||||
($op === '>=' && $prop < $val) ||
|
||||
($op === '<=' && $prop > $val)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert $values into an array with the given $path. You can use
|
||||
* `{n}` and `{s}` elements to insert $data multiple times.
|
||||
*
|
||||
* @param array $data The data to insert into.
|
||||
* @param string $path The path to insert at.
|
||||
* @param array $values The values to insert.
|
||||
* @return array The data with $values inserted.
|
||||
*/
|
||||
public static function insert(array $data, $path, $values = null) {
|
||||
$tokens = explode('.', $path);
|
||||
if (strpos($path, '{') === false) {
|
||||
return self::_simpleOp('insert', $data, $tokens, $values);
|
||||
}
|
||||
|
||||
$token = array_shift($tokens);
|
||||
$nextPath = implode('.', $tokens);
|
||||
foreach ($data as $k => $v) {
|
||||
if (self::_matchToken($k, $token)) {
|
||||
$data[$k] = self::insert($v, $nextPath, $values);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a simple insert/remove operation.
|
||||
*
|
||||
* @param string $op The operation to do.
|
||||
* @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.
|
||||
*/
|
||||
protected static function _simpleOp($op, $data, $path, $values = null) {
|
||||
$_list =& $data;
|
||||
|
||||
$count = count($path);
|
||||
$last = $count - 1;
|
||||
foreach ($path as $i => $key) {
|
||||
if (is_numeric($key) && intval($key) > 0 || $key === '0') {
|
||||
$key = intval($key);
|
||||
}
|
||||
if ($op === 'insert') {
|
||||
if ($i === $last) {
|
||||
$_list[$key] = $values;
|
||||
return $data;
|
||||
}
|
||||
if (!isset($_list[$key])) {
|
||||
$_list[$key] = array();
|
||||
}
|
||||
$_list =& $_list[$key];
|
||||
if (!is_array($_list)) {
|
||||
$_list = array();
|
||||
}
|
||||
} elseif ($op === 'remove') {
|
||||
if ($i === $last) {
|
||||
unset($_list[$key]);
|
||||
return $data;
|
||||
}
|
||||
if (!isset($_list[$key])) {
|
||||
return $data;
|
||||
}
|
||||
$_list =& $_list[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove data matching $path from the $data array.
|
||||
* You can use `{n}` and `{s}` to remove multiple elements
|
||||
* from $data.
|
||||
*
|
||||
* @param array $data The data to operate on
|
||||
* @param string $path A path expression to use to remove.
|
||||
* @return array The modified array.
|
||||
*/
|
||||
public static function remove(array $data, $path) {
|
||||
$tokens = explode('.', $path);
|
||||
if (strpos($path, '{') === false) {
|
||||
return self::_simpleOp('remove', $data, $tokens);
|
||||
}
|
||||
|
||||
$token = array_shift($tokens);
|
||||
$nextPath = implode('.', $tokens);
|
||||
foreach ($data as $k => $v) {
|
||||
$match = self::_matchToken($k, $token);
|
||||
if ($match && is_array($v)) {
|
||||
$data[$k] = self::remove($v, $nextPath);
|
||||
} elseif ($match) {
|
||||
unset($data[$k]);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an associative array using `$keyPath` as the path to build its keys, and optionally
|
||||
* `$valuePath` as path to get the values. If `$valuePath` is not specified, all values will be initialized
|
||||
* to null (useful for Hash::merge). You can optionally group the values by what is obtained when
|
||||
* following the path specified in `$groupPath`.
|
||||
*
|
||||
* @param array $data Array from where to extract keys and values
|
||||
* @param string $keyPath A dot-separated string.
|
||||
* @param string $valuePath A dot-separated string.
|
||||
* @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
|
||||
*/
|
||||
public static function combine(array $data, $keyPath, $valuePath = null, $groupPath = null) {
|
||||
if (empty($data)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if (is_array($keyPath)) {
|
||||
$format = array_shift($keyPath);
|
||||
$keys = self::format($data, $keyPath, $format);
|
||||
} else {
|
||||
$keys = self::extract($data, $keyPath);
|
||||
}
|
||||
if (empty($keys)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if (!empty($valuePath) && is_array($valuePath)) {
|
||||
$format = array_shift($valuePath);
|
||||
$vals = self::format($data, $valuePath, $format);
|
||||
} elseif (!empty($valuePath)) {
|
||||
$vals = self::extract($data, $valuePath);
|
||||
}
|
||||
|
||||
$count = count($keys);
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$vals[$i] = isset($vals[$i]) ? $vals[$i] : null;
|
||||
}
|
||||
|
||||
if ($groupPath !== null) {
|
||||
$group = self::extract($data, $groupPath);
|
||||
if (!empty($group)) {
|
||||
$c = count($keys);
|
||||
for ($i = 0; $i < $c; $i++) {
|
||||
if (!isset($group[$i])) {
|
||||
$group[$i] = 0;
|
||||
}
|
||||
if (!isset($out[$group[$i]])) {
|
||||
$out[$group[$i]] = array();
|
||||
}
|
||||
$out[$group[$i]][$keys[$i]] = $vals[$i];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
if (empty($vals)) {
|
||||
return array();
|
||||
}
|
||||
return array_combine($keys, $vals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formated 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.
|
||||
*
|
||||
* @param array $data Source array from which to extract the data
|
||||
* @param string $paths An array containing one or more Hash::extract()-style key paths
|
||||
* @param string $format Format string into which values will be inserted, see sprintf()
|
||||
* @return array An array of strings extracted from `$path` and formatted with `$format`
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::format
|
||||
* @see sprintf()
|
||||
* @see Hash::extract()
|
||||
*/
|
||||
public static function format(array $data, array $paths, $format) {
|
||||
$extracted = array();
|
||||
$count = count($paths);
|
||||
|
||||
if (!$count) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$extracted[] = self::extract($data, $paths[$i]);
|
||||
}
|
||||
$out = array();
|
||||
$data = $extracted;
|
||||
$count = count($data[0]);
|
||||
|
||||
$countTwo = count($data);
|
||||
for ($j = 0; $j < $count; $j++) {
|
||||
$args = array();
|
||||
for ($i = 0; $i < $countTwo; $i++) {
|
||||
if (array_key_exists($j, $data[$i])) {
|
||||
$args[] = $data[$i][$j];
|
||||
}
|
||||
}
|
||||
$out[] = vsprintf($format, $args);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if one array contains the exact keys and values of another.
|
||||
*
|
||||
* @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
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::contains
|
||||
*/
|
||||
public static function contains(array $data, array $needle) {
|
||||
if (empty($data) || empty($needle)) {
|
||||
return false;
|
||||
}
|
||||
$stack = array();
|
||||
|
||||
while (!empty($needle)) {
|
||||
$key = key($needle);
|
||||
$val = $needle[$key];
|
||||
unset($needle[$key]);
|
||||
|
||||
if (isset($data[$key]) && is_array($val)) {
|
||||
$next = $data[$key];
|
||||
unset($data[$key]);
|
||||
|
||||
if (!empty($val)) {
|
||||
$stack[] = array($val, $next);
|
||||
}
|
||||
} elseif (!isset($data[$key]) || $data[$key] != $val) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($needle) && !empty($stack)) {
|
||||
list($needle, $data) = array_pop($stack);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether or not a given path exists in $data.
|
||||
* This method uses the same path syntax as Hash::extract()
|
||||
*
|
||||
* Checking for paths that could target more than one element will
|
||||
* make sure that at least one matching element exists.
|
||||
*
|
||||
* @param array $data The data to check.
|
||||
* @param string $path The path to check for.
|
||||
* @return boolean Existence of path.
|
||||
* @see Hash::extract()
|
||||
*/
|
||||
public static function check(array $data, $path) {
|
||||
$results = self::extract($data, $path);
|
||||
if (!is_array($results)) {
|
||||
return false;
|
||||
}
|
||||
return count($results) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
return array_filter($data, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function for filtering.
|
||||
*
|
||||
* @param array $var Array to filter.
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function _filter($var) {
|
||||
if ($var === 0 || $var === '0' || !empty($var)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses a multi-dimensional array into a single dimension, using a delimited array path for
|
||||
* each array element's key, i.e. array(array('Foo' => array('Bar' => 'Far'))) becomes
|
||||
* array('0.Foo.Bar' => 'Far').)
|
||||
*
|
||||
* @param array $data Array to flatten
|
||||
* @param string $separator String used to separate array key elements in a path, defaults to '.'
|
||||
* @return array
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::flatten
|
||||
*/
|
||||
public static function flatten(array $data, $separator = '.') {
|
||||
$result = array();
|
||||
$stack = array();
|
||||
$path = null;
|
||||
|
||||
reset($data);
|
||||
while (!empty($data)) {
|
||||
$key = key($data);
|
||||
$element = $data[$key];
|
||||
unset($data[$key]);
|
||||
|
||||
if (is_array($element) && !empty($element)) {
|
||||
if (!empty($data)) {
|
||||
$stack[] = array($data, $path);
|
||||
}
|
||||
$data = $element;
|
||||
reset($data);
|
||||
$path .= $key . $separator;
|
||||
} else {
|
||||
$result[$path . $key] = $element;
|
||||
}
|
||||
|
||||
if (empty($data) && !empty($stack)) {
|
||||
list($data, $path) = array_pop($stack);
|
||||
reset($data);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand/unflattens an string to an array
|
||||
*
|
||||
* For example, unflattens an array that was collapsed with `Hash::flatten()`
|
||||
* into a multi-dimensional array. So, `array('0.Foo.Bar' => 'Far')` becomes
|
||||
* `array(array('Foo' => array('Bar' => 'Far')))`.
|
||||
*
|
||||
* @param array $data Flattened array
|
||||
* @param string $separator The delimiter used
|
||||
* @return array
|
||||
*/
|
||||
public static function expand($data, $separator = '.') {
|
||||
$result = array();
|
||||
foreach ($data as $flat => $value) {
|
||||
$keys = explode($separator, $flat);
|
||||
$keys = array_reverse($keys);
|
||||
$child = array(
|
||||
$keys[0] => $value
|
||||
);
|
||||
array_shift($keys);
|
||||
foreach ($keys as $k) {
|
||||
$child = array(
|
||||
$k => $child
|
||||
);
|
||||
}
|
||||
$result = self::merge($result, $child);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* @param array $data Array to be merged
|
||||
* @param mixed $merge Array to merge with. The argument and all trailing arguments will be array cast when merged
|
||||
* @return array Merged array
|
||||
* @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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
* to get the dimensions of the array.
|
||||
*
|
||||
* @param array $array Array to count dimensions on
|
||||
* @return integer 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) {
|
||||
if (empty($data)) {
|
||||
return 0;
|
||||
}
|
||||
reset($data);
|
||||
$depth = 1;
|
||||
while ($elem = array_shift($data)) {
|
||||
if (is_array($elem)) {
|
||||
$depth += 1;
|
||||
$data =& $elem;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the dimensions of *all* array elements. Useful for finding the maximum
|
||||
* number of dimensions in a mixed array.
|
||||
*
|
||||
* @param array $data Array to count dimensions on
|
||||
* @return integer 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) {
|
||||
$depth = array();
|
||||
if (is_array($data) && reset($data) !== false) {
|
||||
foreach ($data as $value) {
|
||||
$depth[] = self::dimensions((array)$value) + 1;
|
||||
}
|
||||
}
|
||||
return max($depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a callback across all elements in a set.
|
||||
* Can be provided a path to only modify slices of the set.
|
||||
*
|
||||
* @param array $data The data to map over, and extract data out of.
|
||||
* @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.
|
||||
*/
|
||||
public static function map(array $data, $path, $function) {
|
||||
$values = (array)self::extract($data, $path);
|
||||
return array_map($function, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce a set of extracted values using `$function`.
|
||||
*
|
||||
* @param array $data The data to reduce.
|
||||
* @param string $path The path to extract from $data.
|
||||
* @return mixed The reduced value.
|
||||
*/
|
||||
public static function reduce(array $data, $path, $function) {
|
||||
$values = (array)self::extract($data, $path);
|
||||
return array_reduce($values, $function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a callback to a set of extracted values using `$function`.
|
||||
* The function will get the extracted values as the first argument.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* You can easily count the results of an extract using apply().
|
||||
* For example to count the comments on an Article:
|
||||
*
|
||||
* `$count = Hash::apply($data, 'Article.Comment.{n}', 'count');`
|
||||
*
|
||||
* You could also use a function like `array_sum` to sum the results.
|
||||
*
|
||||
* `$total = Hash::apply($data, '{n}.Item.price', 'array_sum');`
|
||||
*
|
||||
* @param array $data The data to reduce.
|
||||
* @param string $path The path to extract from $data.
|
||||
* @return mixed The results of the applied method.
|
||||
*/
|
||||
public static function apply(array $data, $path, $function) {
|
||||
$values = (array)self::extract($data, $path);
|
||||
return call_user_func($function, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts an array by any value, determined by a Set-compatible path
|
||||
*
|
||||
* ### Sort directions
|
||||
*
|
||||
* - `asc` Sort ascending.
|
||||
* - `desc` Sort descending.
|
||||
*
|
||||
* ## 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.
|
||||
*
|
||||
* @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'.
|
||||
* @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') {
|
||||
if (empty($data)) {
|
||||
return array();
|
||||
}
|
||||
$originalKeys = array_keys($data);
|
||||
$numeric = is_numeric(implode('', $originalKeys));
|
||||
if ($numeric) {
|
||||
$data = array_values($data);
|
||||
}
|
||||
$sortValues = self::extract($data, $path);
|
||||
$sortCount = count($sortValues);
|
||||
$dataCount = count($data);
|
||||
|
||||
// Make sortValues match the data length, as some keys could be missing
|
||||
// the sorted value path.
|
||||
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');
|
||||
|
||||
$dir = strtolower($dir);
|
||||
$type = strtolower($type);
|
||||
if ($type == 'natural' && version_compare(PHP_VERSION, '5.4.0', '<')) {
|
||||
$type == 'regular';
|
||||
}
|
||||
if ($dir === 'asc') {
|
||||
$dir = SORT_ASC;
|
||||
} else {
|
||||
$dir = SORT_DESC;
|
||||
}
|
||||
if ($type === 'numeric') {
|
||||
$type = SORT_NUMERIC;
|
||||
} elseif ($type === 'string') {
|
||||
$type = SORT_STRING;
|
||||
} elseif ($type === 'natural') {
|
||||
$type = SORT_NATURAL;
|
||||
} else {
|
||||
$type = SORT_REGULAR;
|
||||
}
|
||||
array_multisort($values, $dir, $type, $keys, $dir, $type);
|
||||
$sorted = array();
|
||||
$keys = array_unique($keys);
|
||||
|
||||
foreach ($keys as $k) {
|
||||
if ($numeric) {
|
||||
$sorted[] = $data[$k];
|
||||
continue;
|
||||
}
|
||||
if (isset($originalKeys[$k])) {
|
||||
$sorted[$originalKeys[$k]] = $data[$originalKeys[$k]];
|
||||
} else {
|
||||
$sorted[$k] = $data[$k];
|
||||
}
|
||||
}
|
||||
return $sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for sort()
|
||||
* Sqaushes 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.
|
||||
* @return array
|
||||
*/
|
||||
protected static function _squash($data, $key = null) {
|
||||
$stack = array();
|
||||
foreach ($data as $k => $r) {
|
||||
$id = $k;
|
||||
if (!is_null($key)) {
|
||||
$id = $key;
|
||||
}
|
||||
if (is_array($r) && !empty($r)) {
|
||||
$stack = array_merge($stack, self::_squash($r, $id));
|
||||
} else {
|
||||
$stack[] = array('id' => $id, 'value' => $r);
|
||||
}
|
||||
}
|
||||
return $stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference between two complex arrays.
|
||||
* This method differs from the built-in array_diff() in that it will preserve keys
|
||||
* and work on multi-dimensional arrays.
|
||||
*
|
||||
* @param array $data First value
|
||||
* @param array $compare Second value
|
||||
* @return array Returns the key => value pairs that are not common in $data and $compare
|
||||
* The expression for this function is ($data - $compare) + ($compare - ($data - $compare))
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::diff
|
||||
*/
|
||||
public static function diff(array $data, $compare) {
|
||||
if (empty($data)) {
|
||||
return (array)$compare;
|
||||
}
|
||||
if (empty($compare)) {
|
||||
return (array)$data;
|
||||
}
|
||||
$intersection = array_intersect_key($data, $compare);
|
||||
while (($key = key($intersection)) !== null) {
|
||||
if ($data[$key] == $compare[$key]) {
|
||||
unset($data[$key]);
|
||||
unset($compare[$key]);
|
||||
}
|
||||
next($intersection);
|
||||
}
|
||||
return $data + $compare;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the difference between $data and $push 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.
|
||||
*/
|
||||
public static function mergeDiff(array $data, $compare) {
|
||||
if (empty($data) && !empty($compare)) {
|
||||
return $compare;
|
||||
}
|
||||
if (empty($compare)) {
|
||||
return $data;
|
||||
}
|
||||
foreach ($compare as $key => $value) {
|
||||
if (!array_key_exists($key, $data)) {
|
||||
$data[$key] = $value;
|
||||
} elseif (is_array($value)) {
|
||||
$data[$key] = self::mergeDiff($data[$key], $compare[$key]);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return array
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::normalize
|
||||
*/
|
||||
public static function normalize(array $data, $assoc = true) {
|
||||
$keys = array_keys($data);
|
||||
$count = count($keys);
|
||||
$numeric = true;
|
||||
|
||||
if (!$assoc) {
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
if (!is_int($keys[$i])) {
|
||||
$numeric = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$numeric || $assoc) {
|
||||
$newList = array();
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
if (is_int($keys[$i])) {
|
||||
$newList[$data[$keys[$i]]] = null;
|
||||
} else {
|
||||
$newList[$keys[$i]] = $data[$keys[$i]];
|
||||
}
|
||||
}
|
||||
$data = $newList;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes in a flat array and returns a nested array
|
||||
*
|
||||
* ### Options:
|
||||
*
|
||||
* - `children` The key name to use in the resultset for children.
|
||||
* - `idPath` The path to a key that identifies each entry. Should be
|
||||
* compatible with Hash::extract(). Defaults to `{n}.$alias.id`
|
||||
* - `parentPath` The path to a key that identifies the parent of each entry.
|
||||
* Should be compatible with Hash::extract(). Defaults to `{n}.$alias.parent_id`
|
||||
* - `root` The id of the desired top-most result.
|
||||
*
|
||||
* @param array $data The data to nest.
|
||||
* @param array $options Options are:
|
||||
* @return array of results, nested
|
||||
* @see Hash::extract()
|
||||
*/
|
||||
public static function nest(array $data, $options = array()) {
|
||||
if (!$data) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$alias = key(current($data));
|
||||
$options += array(
|
||||
'idPath' => "{n}.$alias.id",
|
||||
'parentPath' => "{n}.$alias.parent_id",
|
||||
'children' => 'children',
|
||||
'root' => null
|
||||
);
|
||||
|
||||
$return = $idMap = array();
|
||||
$ids = self::extract($data, $options['idPath']);
|
||||
|
||||
$idKeys = explode('.', $options['idPath']);
|
||||
array_shift($idKeys);
|
||||
|
||||
$parentKeys = explode('.', $options['parentPath']);
|
||||
array_shift($parentKeys);
|
||||
|
||||
foreach ($data as $result) {
|
||||
$result[$options['children']] = array();
|
||||
|
||||
$id = self::get($result, $idKeys);
|
||||
$parentId = self::get($result, $parentKeys);
|
||||
|
||||
if (isset($idMap[$id][$options['children']])) {
|
||||
$idMap[$id] = array_merge($result, (array)$idMap[$id]);
|
||||
} else {
|
||||
$idMap[$id] = array_merge($result, array($options['children'] => array()));
|
||||
}
|
||||
if (!$parentId || !in_array($parentId, $ids)) {
|
||||
$return[] =& $idMap[$id];
|
||||
} else {
|
||||
$idMap[$parentId][$options['children']][] =& $idMap[$id];
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['root']) {
|
||||
$root = $options['root'];
|
||||
} else {
|
||||
$root = self::get($return[0], $parentKeys);
|
||||
}
|
||||
|
||||
foreach ($return as $i => $result) {
|
||||
$id = self::get($result, $idKeys);
|
||||
$parentId = self::get($result, $parentKeys);
|
||||
if ($id !== $root && $parentId != $root) {
|
||||
unset($return[$i]);
|
||||
}
|
||||
}
|
||||
return array_values($return);
|
||||
}
|
||||
|
||||
}
|
||||
552
lib/Cake/Utility/Inflector.php
Normal file
552
lib/Cake/Utility/Inflector.php
Normal file
|
|
@ -0,0 +1,552 @@
|
|||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright 2005-2012, 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)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Pluralize and singularize English words.
|
||||
*
|
||||
* Inflector pluralizes and singularizes English nouns.
|
||||
* Used by Cake's naming conventions throughout the framework.
|
||||
*
|
||||
* @package Cake.Utility
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/inflector.html
|
||||
*/
|
||||
class Inflector {
|
||||
|
||||
/**
|
||||
* Plural inflector rules
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_plural = array(
|
||||
'rules' => array(
|
||||
'/(s)tatus$/i' => '\1\2tatuses',
|
||||
'/(quiz)$/i' => '\1zes',
|
||||
'/^(ox)$/i' => '\1\2en',
|
||||
'/([m|l])ouse$/i' => '\1ice',
|
||||
'/(matr|vert|ind)(ix|ex)$/i' => '\1ices',
|
||||
'/(x|ch|ss|sh)$/i' => '\1es',
|
||||
'/([^aeiouy]|qu)y$/i' => '\1ies',
|
||||
'/(hive)$/i' => '\1s',
|
||||
'/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
|
||||
'/sis$/i' => 'ses',
|
||||
'/([ti])um$/i' => '\1a',
|
||||
'/(p)erson$/i' => '\1eople',
|
||||
'/(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',
|
||||
'/us$/i' => 'uses',
|
||||
'/(alias)$/i' => '\1es',
|
||||
'/(ax|cris|test)is$/i' => '\1es',
|
||||
'/s$/' => 's',
|
||||
'/^$/' => '',
|
||||
'/$/' => 's',
|
||||
),
|
||||
'uninflected' => array(
|
||||
'.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', 'people', 'cookie'
|
||||
),
|
||||
'irregular' => array(
|
||||
'atlas' => 'atlases',
|
||||
'beef' => 'beefs',
|
||||
'brother' => 'brothers',
|
||||
'cafe' => 'cafes',
|
||||
'child' => 'children',
|
||||
'cookie' => 'cookies',
|
||||
'corpus' => 'corpuses',
|
||||
'cow' => 'cows',
|
||||
'ganglion' => 'ganglions',
|
||||
'genie' => 'genies',
|
||||
'genus' => 'genera',
|
||||
'graffito' => 'graffiti',
|
||||
'hoof' => 'hoofs',
|
||||
'loaf' => 'loaves',
|
||||
'man' => 'men',
|
||||
'money' => 'monies',
|
||||
'mongoose' => 'mongooses',
|
||||
'move' => 'moves',
|
||||
'mythos' => 'mythoi',
|
||||
'niche' => 'niches',
|
||||
'numen' => 'numina',
|
||||
'occiput' => 'occiputs',
|
||||
'octopus' => 'octopuses',
|
||||
'opus' => 'opuses',
|
||||
'ox' => 'oxen',
|
||||
'penis' => 'penises',
|
||||
'person' => 'people',
|
||||
'sex' => 'sexes',
|
||||
'soliloquy' => 'soliloquies',
|
||||
'testis' => 'testes',
|
||||
'trilby' => 'trilbys',
|
||||
'turf' => 'turfs'
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Singular inflector rules
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_singular = array(
|
||||
'rules' => array(
|
||||
'/(s)tatuses$/i' => '\1\2tatus',
|
||||
'/^(.*)(menu)s$/i' => '\1\2',
|
||||
'/(quiz)zes$/i' => '\\1',
|
||||
'/(matr)ices$/i' => '\1ix',
|
||||
'/(vert|ind)ices$/i' => '\1ex',
|
||||
'/^(ox)en/i' => '\1',
|
||||
'/(alias)(es)*$/i' => '\1',
|
||||
'/(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',
|
||||
'/(o)es$/i' => '\1',
|
||||
'/ouses$/' => 'ouse',
|
||||
'/([^a])uses$/' => '\1us',
|
||||
'/([m|l])ice$/i' => '\1ouse',
|
||||
'/(x|ch|ss|sh)es$/i' => '\1',
|
||||
'/(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',
|
||||
'/(^analy)ses$/i' => '\1sis',
|
||||
'/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
|
||||
'/([ti])a$/i' => '\1um',
|
||||
'/(p)eople$/i' => '\1\2erson',
|
||||
'/(m)en$/i' => '\1an',
|
||||
'/(c)hildren$/i' => '\1\2hild',
|
||||
'/(n)ews$/i' => '\1\2ews',
|
||||
'/eaus$/' => 'eau',
|
||||
'/^(.*us)$/' => '\\1',
|
||||
'/s$/i' => ''
|
||||
),
|
||||
'uninflected' => array(
|
||||
'.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', '.*ss'
|
||||
),
|
||||
'irregular' => array(
|
||||
'foes' => 'foe',
|
||||
'waves' => 'wave',
|
||||
'curves' => 'curve'
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Words that should not be inflected
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_uninflected = array(
|
||||
'Amoyese', 'bison', 'Borghese', 'bream', 'breeches', 'britches', 'buffalo', 'cantus',
|
||||
'carp', 'chassis', 'clippers', 'cod', 'coitus', 'Congoese', 'contretemps', 'corps',
|
||||
'debris', 'diabetes', 'djinn', 'eland', 'elk', 'equipment', 'Faroese', 'flounder',
|
||||
'Foochowese', 'gallows', 'Genevese', 'Genoese', 'Gilbertese', 'graffiti',
|
||||
'headquarters', 'herpes', 'hijinks', 'Hottentotese', 'information', 'innings',
|
||||
'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',
|
||||
'sea[- ]bass', 'series', 'Shavese', 'shears', 'siemens', 'species', 'swine', 'testes',
|
||||
'trousers', 'trout', 'tuna', 'Vermontese', 'Wenchowese', 'whiting', 'wildebeest',
|
||||
'Yengeese'
|
||||
);
|
||||
|
||||
/**
|
||||
* Default map of accented and special characters to ASCII characters
|
||||
*
|
||||
* @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',
|
||||
'/IJ/' => 'IJ',
|
||||
'/ij/' => 'ij',
|
||||
'/Œ/' => 'OE',
|
||||
'/ƒ/' => 'f'
|
||||
);
|
||||
|
||||
/**
|
||||
* Method cache array.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_cache = array();
|
||||
|
||||
/**
|
||||
* The initial state of Inflector so reset() works.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_initialState = array();
|
||||
|
||||
/**
|
||||
* Cache inflected values, and return if already available
|
||||
*
|
||||
* @param string $type Inflection type
|
||||
* @param string $key Original value
|
||||
* @param string $value Inflected value
|
||||
* @return string Inflected value, from cache
|
||||
*/
|
||||
protected static function _cache($type, $key, $value = false) {
|
||||
$key = '_' . $key;
|
||||
$type = '_' . $type;
|
||||
if ($value !== false) {
|
||||
self::$_cache[$type][$key] = $value;
|
||||
return $value;
|
||||
}
|
||||
if (!isset(self::$_cache[$type][$key])) {
|
||||
return false;
|
||||
}
|
||||
return self::$_cache[$type][$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears Inflectors inflected value caches. And resets the inflection
|
||||
* rules to the initial values.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reset() {
|
||||
if (empty(self::$_initialState)) {
|
||||
self::$_initialState = get_class_vars('Inflector');
|
||||
return;
|
||||
}
|
||||
foreach (self::$_initialState as $key => $val) {
|
||||
if ($key != '_initialState') {
|
||||
self::${$key} = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds custom inflection $rules, of either 'plural', 'singular' or 'transliteration' $type.
|
||||
*
|
||||
* ### Usage:
|
||||
*
|
||||
* {{{
|
||||
* Inflector::rules('plural', array('/^(inflect)or$/i' => '\1ables'));
|
||||
* Inflector::rules('plural', array(
|
||||
* 'rules' => array('/^(inflect)ors$/i' => '\1ables'),
|
||||
* 'uninflected' => array('dontinflectme'),
|
||||
* '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
|
||||
* new rules that are being defined in $rules.
|
||||
* @return void
|
||||
*/
|
||||
public static function rules($type, $rules, $reset = false) {
|
||||
$var = '_' . $type;
|
||||
|
||||
switch ($type) {
|
||||
case 'transliteration':
|
||||
if ($reset) {
|
||||
self::$_transliteration = $rules;
|
||||
} else {
|
||||
self::$_transliteration = $rules + self::$_transliteration;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
foreach ($rules as $rule => $pattern) {
|
||||
if (is_array($pattern)) {
|
||||
if ($reset) {
|
||||
self::${$var}[$rule] = $pattern;
|
||||
} else {
|
||||
if ($rule === 'uninflected') {
|
||||
self::${$var}[$rule] = array_merge($pattern, self::${$var}[$rule]);
|
||||
} else {
|
||||
self::${$var}[$rule] = $pattern + self::${$var}[$rule];
|
||||
}
|
||||
}
|
||||
unset($rules[$rule], self::${$var}['cache' . ucfirst($rule)]);
|
||||
if (isset(self::${$var}['merged'][$rule])) {
|
||||
unset(self::${$var}['merged'][$rule]);
|
||||
}
|
||||
if ($type === 'plural') {
|
||||
self::$_cache['pluralize'] = self::$_cache['tableize'] = array();
|
||||
} elseif ($type === 'singular') {
|
||||
self::$_cache['singularize'] = array();
|
||||
}
|
||||
}
|
||||
}
|
||||
self::${$var}['rules'] = $rules + self::${$var}['rules'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return $word in plural form.
|
||||
*
|
||||
* @param string $word Word in singular
|
||||
* @return string Word in plural
|
||||
* @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(self::$_plural['merged']['irregular'])) {
|
||||
self::$_plural['merged']['irregular'] = self::$_plural['irregular'];
|
||||
}
|
||||
|
||||
if (!isset(self::$_plural['merged']['uninflected'])) {
|
||||
self::$_plural['merged']['uninflected'] = array_merge(self::$_plural['uninflected'], self::$_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 (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('/^(' . self::$_plural['cacheUninflected'] . ')$/i', $word, $regs)) {
|
||||
self::$_cache['pluralize'][$word] = $word;
|
||||
return $word;
|
||||
}
|
||||
|
||||
foreach (self::$_plural['rules'] as $rule => $replacement) {
|
||||
if (preg_match($rule, $word)) {
|
||||
self::$_cache['pluralize'][$word] = preg_replace($rule, $replacement, $word);
|
||||
return self::$_cache['pluralize'][$word];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return $word in singular form.
|
||||
*
|
||||
* @param string $word Word in plural
|
||||
* @return string Word in singular
|
||||
* @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(self::$_singular['merged']['uninflected'])) {
|
||||
self::$_singular['merged']['uninflected'] = array_merge(
|
||||
self::$_singular['uninflected'],
|
||||
self::$_uninflected
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset(self::$_singular['merged']['irregular'])) {
|
||||
self::$_singular['merged']['irregular'] = array_merge(
|
||||
self::$_singular['irregular'],
|
||||
array_flip(self::$_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 (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('/^(' . self::$_singular['cacheUninflected'] . ')$/i', $word, $regs)) {
|
||||
self::$_cache['singularize'][$word] = $word;
|
||||
return $word;
|
||||
}
|
||||
|
||||
foreach (self::$_singular['rules'] as $rule => $replacement) {
|
||||
if (preg_match($rule, $word)) {
|
||||
self::$_cache['singularize'][$word] = preg_replace($rule, $replacement, $word);
|
||||
return self::$_cache['singularize'][$word];
|
||||
}
|
||||
}
|
||||
self::$_cache['singularize'][$word] = $word;
|
||||
return $word;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given lower_case_and_underscored_word as a CamelCased word.
|
||||
*
|
||||
* @param string $lowerCaseAndUnderscoredWord Word to camelize
|
||||
* @return string Camelized word. LikeThis.
|
||||
* @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))) {
|
||||
$result = str_replace(' ', '', Inflector::humanize($lowerCaseAndUnderscoredWord));
|
||||
self::_cache(__FUNCTION__, $lowerCaseAndUnderscoredWord, $result);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given camelCasedWord as an underscored_word.
|
||||
*
|
||||
* @param string $camelCasedWord Camel-cased word to be "underscorized"
|
||||
* @return string Underscore-syntaxed version of the $camelCasedWord
|
||||
* @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);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given underscored_word_group as a Human Readable Word Group.
|
||||
* (Underscores are replaced by spaces and capitalized following words.)
|
||||
*
|
||||
* @param string $lowerCaseAndUnderscoredWord String to be made more readable
|
||||
* @return string Human-readable string
|
||||
* @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);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns corresponding table name for given model $className. ("people" for the model class "Person").
|
||||
*
|
||||
* @param string $className Name of class to get database table name for
|
||||
* @return string Name of the database table for given class
|
||||
* @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))) {
|
||||
$result = Inflector::pluralize(Inflector::underscore($className));
|
||||
self::_cache(__FUNCTION__, $className, $result);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Cake model class name ("Person" for the database table "people".) for given database table.
|
||||
*
|
||||
* @param string $tableName Name of database table to get class name for
|
||||
* @return string Class name
|
||||
* @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))) {
|
||||
$result = Inflector::camelize(Inflector::singularize($tableName));
|
||||
self::_cache(__FUNCTION__, $tableName, $result);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns camelBacked version of an underscored string.
|
||||
*
|
||||
* @param string $string
|
||||
* @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))) {
|
||||
$camelized = Inflector::camelize(Inflector::underscore($string));
|
||||
$replace = strtolower(substr($camelized, 0, 1));
|
||||
$result = preg_replace('/\\w/', $replace, $camelized, 1);
|
||||
self::_cache(__FUNCTION__, $string, $result);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string with all spaces converted to underscores (by default), accented
|
||||
* characters converted to non-accented characters, and non word characters removed.
|
||||
*
|
||||
* @param string $string the string you want to slug
|
||||
* @param string $replacement will replace keys in map
|
||||
* @return string
|
||||
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/inflector.html#Inflector::slug
|
||||
*/
|
||||
public static function slug($string, $replacement = '_') {
|
||||
$quotedReplacement = preg_quote($replacement, '/');
|
||||
|
||||
$merge = array(
|
||||
'/[^\s\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]/mu' => ' ',
|
||||
'/\\s+/' => $replacement,
|
||||
sprintf('/^[%s]+|[%s]+$/', $quotedReplacement, $quotedReplacement) => '',
|
||||
);
|
||||
|
||||
$map = self::$_transliteration + $merge;
|
||||
return preg_replace(array_keys($map), array_values($map), $string);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Store the initial state
|
||||
Inflector::reset();
|
||||
326
lib/Cake/Utility/ObjectCollection.php
Normal file
326
lib/Cake/Utility/ObjectCollection.php
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright 2005-2012, 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)
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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
|
||||
* when loaded.
|
||||
*
|
||||
* @package Cake.Utility
|
||||
* @since CakePHP(tm) v 2.0
|
||||
*/
|
||||
abstract class ObjectCollection {
|
||||
|
||||
/**
|
||||
* List of the currently-enabled objects
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_enabled = array();
|
||||
|
||||
/**
|
||||
* A hash of loaded objects, indexed by name
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_loaded = array();
|
||||
|
||||
/**
|
||||
* Default object priority. A non zero integer.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $defaultPriority = 10;
|
||||
|
||||
/**
|
||||
* Loads a new object onto the collection. Can throw a variety of exceptions
|
||||
*
|
||||
* Implementations of this class support a `$options['enabled']` flag which enables/disables
|
||||
* a loaded object.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
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
|
||||
* order they were attached.
|
||||
*
|
||||
* ### Options
|
||||
*
|
||||
* - `breakOn` Set to the value or values you want the callback propagation to stop on.
|
||||
* 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.
|
||||
* Defaults to `false`.
|
||||
*
|
||||
* - `collectReturn` Set to true to collect the return of each object into an array.
|
||||
* This array of return values will be returned from the trigger() call. Defaults to `false`.
|
||||
*
|
||||
* - `modParams` Allows each object the callback gets called on to modify the parameters to the next object.
|
||||
* Setting modParams to an integer value will allow you to modify the parameter with that index.
|
||||
* 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
|
||||
* 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)
|
||||
* @param array $params Array of parameters for the triggered callback.
|
||||
* @param array $options Array of options.
|
||||
* @return mixed Either the last result or all results if collectReturn is on.
|
||||
* @throws CakeException when modParams is used with an index that does not exist.
|
||||
*/
|
||||
public function trigger($callback, $params = array(), $options = array()) {
|
||||
if (empty($this->_enabled)) {
|
||||
return true;
|
||||
}
|
||||
if ($callback instanceof CakeEvent) {
|
||||
$event = $callback;
|
||||
if (is_array($event->data)) {
|
||||
$params =& $event->data;
|
||||
}
|
||||
if (empty($event->omitSubject)) {
|
||||
$subject = $event->subject();
|
||||
}
|
||||
|
||||
foreach (array('break', 'breakOn', 'collectReturn', 'modParams') as $opt) {
|
||||
if (isset($event->{$opt})) {
|
||||
$options[$opt] = $event->{$opt};
|
||||
}
|
||||
}
|
||||
$parts = explode('.', $event->name());
|
||||
$callback = array_pop($parts);
|
||||
}
|
||||
$options = array_merge(
|
||||
array(
|
||||
'break' => false,
|
||||
'breakOn' => false,
|
||||
'collectReturn' => false,
|
||||
'modParams' => false
|
||||
),
|
||||
$options
|
||||
);
|
||||
$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.'));
|
||||
}
|
||||
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'] ||
|
||||
(is_array($options['breakOn']) && in_array($result, $options['breakOn'], true)))
|
||||
) {
|
||||
return $result;
|
||||
} elseif ($options['modParams'] !== false && !in_array($result, array(true, false, null), true)) {
|
||||
$params[$options['modParams']] = $result;
|
||||
}
|
||||
}
|
||||
if ($options['modParams'] !== false) {
|
||||
return $params[$options['modParams']];
|
||||
}
|
||||
return $options['collectReturn'] ? $collected : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide public read access to the loaded objects
|
||||
*
|
||||
* @param string $name Name of property to read
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($name) {
|
||||
if (isset($this->_loaded[$name])) {
|
||||
return $this->_loaded[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide isset access to _loaded
|
||||
*
|
||||
* @param string $name Name of object being checked.
|
||||
* @return boolean
|
||||
*/
|
||||
public function __isset($name) {
|
||||
return isset($this->_loaded[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
* @return void
|
||||
*/
|
||||
public function enable($name, $prioritize = true) {
|
||||
$enabled = false;
|
||||
foreach ((array)$name as $object) {
|
||||
if (isset($this->_loaded[$object]) && !isset($this->_enabled[$object])) {
|
||||
$priority = isset($this->_loaded[$object]->settings['priority']) ? $this->_loaded[$object]->settings['priority'] : $this->defaultPriority;
|
||||
$this->_enabled[$object] = array($priority);
|
||||
$enabled = true;
|
||||
}
|
||||
}
|
||||
if ($prioritize && $enabled) {
|
||||
$this->prioritize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prioritize list of enabled object
|
||||
*
|
||||
* @return array Prioritized list of object
|
||||
*/
|
||||
public function prioritize() {
|
||||
$i = 1;
|
||||
foreach ($this->_enabled as $name => $priority) {
|
||||
$priority[1] = $i++;
|
||||
$this->_enabled[$name] = $priority;
|
||||
}
|
||||
asort($this->_enabled);
|
||||
return $this->_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set priority for an object or array of objects
|
||||
*
|
||||
* @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
|
||||
* @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;
|
||||
}
|
||||
$this->_loaded[$obj]->settings['priority'] = $prio;
|
||||
if (isset($this->_enabled[$obj])) {
|
||||
$this->_enabled[$obj] = array($prio);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->prioritize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables callbacks on a 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)
|
||||
* @return void
|
||||
*/
|
||||
public function disable($name) {
|
||||
foreach ((array)$name as $object) {
|
||||
unset($this->_enabled[$object]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* 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)) {
|
||||
return isset($this->_enabled[$name]);
|
||||
}
|
||||
return array_keys($this->_enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function attached($name = null) {
|
||||
if (!empty($name)) {
|
||||
return isset($this->_loaded[$name]);
|
||||
}
|
||||
return array_keys($this->_loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the object to remove from the collection
|
||||
*
|
||||
* @param string $name Name of the object to delete.
|
||||
* @return void
|
||||
*/
|
||||
public function unload($name) {
|
||||
list($plugin, $name) = pluginSplit($name);
|
||||
unset($this->_loaded[$name]);
|
||||
unset($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
|
||||
* @return array Loaded objects
|
||||
*/
|
||||
public function set($name = null, $object = null) {
|
||||
if (!empty($name) && !empty($object)) {
|
||||
list($plugin, $name) = pluginSplit($name);
|
||||
$this->_loaded[$name] = $object;
|
||||
}
|
||||
return $this->_loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes an object array, creates an array that makes lazy loading
|
||||
* easier
|
||||
*
|
||||
* @param array $objects Array of child objects to normalize.
|
||||
* @return array Array of normalized objects.
|
||||
*/
|
||||
public static function normalizeObjectArray($objects) {
|
||||
$normal = array();
|
||||
foreach ($objects as $i => $objectName) {
|
||||
$options = array();
|
||||
if (!is_int($i)) {
|
||||
$options = (array)$objectName;
|
||||
$objectName = $i;
|
||||
}
|
||||
list($plugin, $name) = pluginSplit($objectName);
|
||||
$normal[$name] = array('class' => $objectName, 'settings' => $options);
|
||||
}
|
||||
return $normal;
|
||||
}
|
||||
|
||||
}
|
||||
264
lib/Cake/Utility/Sanitize.php
Normal file
264
lib/Cake/Utility/Sanitize.php
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
<?php
|
||||
/**
|
||||
* Washes strings from unwanted noise.
|
||||
*
|
||||
* 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)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright 2005-2012, 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)
|
||||
*/
|
||||
|
||||
App::import('Model', 'ConnectionManager');
|
||||
|
||||
/**
|
||||
* Data Sanitization.
|
||||
*
|
||||
* Removal of alphanumeric characters, SQL-safe slash-added strings, HTML-friendly strings,
|
||||
* and all of the above on arrays.
|
||||
*
|
||||
* @package Cake.Utility
|
||||
*/
|
||||
class Sanitize {
|
||||
|
||||
/**
|
||||
* Removes any non-alphanumeric characters.
|
||||
*
|
||||
* @param string $string String to sanitize
|
||||
* @param array $allowed An array of additional characters that are not to be removed.
|
||||
* @return string Sanitized string
|
||||
*/
|
||||
public static function paranoid($string, $allowed = array()) {
|
||||
$allow = null;
|
||||
if (!empty($allowed)) {
|
||||
foreach ($allowed as $value) {
|
||||
$allow .= "\\$value";
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return $cleaned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a string SQL-safe.
|
||||
*
|
||||
* @param string $string String to sanitize
|
||||
* @param string $connection Database connection being used
|
||||
* @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;
|
||||
}
|
||||
$string = $db->value($string, 'string');
|
||||
if ($string[0] === 'N') {
|
||||
$string = substr($string, 2);
|
||||
} else {
|
||||
$string = substr($string, 1);
|
||||
}
|
||||
|
||||
$string = substr($string, 0, -1);
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns given string safe for display as HTML. Renders entities.
|
||||
*
|
||||
* strip_tags() does not validating HTML syntax or structure, so it might strip whole passages
|
||||
* with broken HTML.
|
||||
*
|
||||
* ### Options:
|
||||
*
|
||||
* - 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
|
||||
*
|
||||
* @param string $string String from where to strip tags
|
||||
* @param array $options Array of options to use.
|
||||
* @return string Sanitized string
|
||||
*/
|
||||
public static function html($string, $options = array()) {
|
||||
static $defaultCharset = false;
|
||||
if ($defaultCharset === false) {
|
||||
$defaultCharset = Configure::read('App.encoding');
|
||||
if ($defaultCharset === null) {
|
||||
$defaultCharset = 'UTF-8';
|
||||
}
|
||||
}
|
||||
$default = array(
|
||||
'remove' => false,
|
||||
'charset' => $defaultCharset,
|
||||
'quotes' => ENT_QUOTES,
|
||||
'double' => true
|
||||
);
|
||||
|
||||
$options = array_merge($default, $options);
|
||||
|
||||
if ($options['remove']) {
|
||||
$string = strip_tags($string);
|
||||
}
|
||||
|
||||
return htmlentities($string, $options['quotes'], $options['charset'], $options['double']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips extra whitespace from output
|
||||
*
|
||||
* @param string $str String to 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips image tags from output
|
||||
*
|
||||
* @param string $str String to 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips scripts and stylesheets from output
|
||||
*
|
||||
* @param string $str String to sanitize
|
||||
* @return string String with <script>, <style>, <link>, <img> elements removed.
|
||||
*/
|
||||
public static function stripScripts($str) {
|
||||
return preg_replace('/(<link[^>]+rel="[^"]*stylesheet"[^>]*>|<img[^>]*>|style="[^"]*")|<script[^>]*>.*?<\/script>|<style[^>]*>.*?<\/style>|<!--.*?-->/is', '', $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips extra whitespace, images, scripts and stylesheets from output
|
||||
*
|
||||
* @param string $str String to sanitize
|
||||
* @return string sanitized string
|
||||
*/
|
||||
public static function stripAll($str) {
|
||||
$str = Sanitize::stripWhitespace($str);
|
||||
$str = Sanitize::stripImages($str);
|
||||
$str = Sanitize::stripScripts($str);
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the specified tags from output. First parameter is string from
|
||||
* where to remove tags. All subsequent parameters are tags.
|
||||
*
|
||||
* Ex.`$clean = Sanitize::stripTags($dirty, 'b', 'p', 'div');`
|
||||
*
|
||||
* Will remove all `<b>`, `<p>`, and `<div>` tags from the $dirty string.
|
||||
*
|
||||
* @param string $str,... String to sanitize
|
||||
* @return string sanitized String
|
||||
*/
|
||||
public static function stripTags($str) {
|
||||
$params = func_get_args();
|
||||
|
||||
for ($i = 1, $count = count($params); $i < $count; $i++) {
|
||||
$str = preg_replace('/<' . $params[$i] . '\b[^>]*>/i', '', $str);
|
||||
$str = preg_replace('/<\/' . $params[$i] . '[^>]*>/i', '', $str);
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes given array or value for safe input. Use the options to specify
|
||||
* the connection to use, and what filters should be applied (with a boolean
|
||||
* value). Valid filters:
|
||||
*
|
||||
* - odd_spaces - removes any non space whitespace characters
|
||||
* - encode - Encode any html entities. Encode must be true for the `remove_html` to work.
|
||||
* - dollar - Escape `$` with `\$`
|
||||
* - carriage - Remove `\r`
|
||||
* - unicode -
|
||||
* - escape - Should the string be SQL escaped.
|
||||
* - backslash -
|
||||
* - remove_html - Strip HTML with strip_tags. `encode` must be true for this option to work.
|
||||
*
|
||||
* @param string|array $data Data to sanitize
|
||||
* @param string|array $options If string, DB connection being used, otherwise set of options
|
||||
* @return mixed Sanitized data
|
||||
*/
|
||||
public static function clean($data, $options = array()) {
|
||||
if (empty($data)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if (is_string($options)) {
|
||||
$options = array('connection' => $options);
|
||||
} elseif (!is_array($options)) {
|
||||
$options = array();
|
||||
}
|
||||
|
||||
$options = array_merge(array(
|
||||
'connection' => 'default',
|
||||
'odd_spaces' => true,
|
||||
'remove_html' => false,
|
||||
'encode' => true,
|
||||
'dollar' => true,
|
||||
'carriage' => true,
|
||||
'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("/&#([0-9]+);/s", "&#\\1;", $data);
|
||||
}
|
||||
if ($options['escape']) {
|
||||
$data = Sanitize::escape($data, $options['connection']);
|
||||
}
|
||||
if ($options['backslash']) {
|
||||
$data = preg_replace("/\\\(?!&#|\?#)/", "\\", $data);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
188
lib/Cake/Utility/Security.php
Normal file
188
lib/Cake/Utility/Security.php
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
/**
|
||||
* Core Security
|
||||
*
|
||||
* PHP 5
|
||||
*
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright 2005-2012, 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)
|
||||
*/
|
||||
|
||||
App::uses('String', 'Utility');
|
||||
|
||||
/**
|
||||
* Security Library contains utility methods related to security
|
||||
*
|
||||
* @package Cake.Utility
|
||||
*/
|
||||
class Security {
|
||||
|
||||
/**
|
||||
* Default hash method
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $hashType = null;
|
||||
|
||||
/**
|
||||
* Get allowed minutes of inactivity based on security level.
|
||||
*
|
||||
* @return integer Allowed inactivity in minutes
|
||||
*/
|
||||
public static function inactiveMins() {
|
||||
switch (Configure::read('Security.level')) {
|
||||
case 'high':
|
||||
return 10;
|
||||
case 'medium':
|
||||
return 100;
|
||||
case 'low':
|
||||
default:
|
||||
return 300;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate authorization hash.
|
||||
*
|
||||
* @return string Hash
|
||||
*/
|
||||
public static function generateAuthKey() {
|
||||
return Security::hash(String::uuid());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate authorization hash.
|
||||
*
|
||||
* @param string $authKey Authorization hash
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function validateAuthKey($authKey) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a hash from string using given method.
|
||||
* Fallback on next available method.
|
||||
*
|
||||
* @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)
|
||||
* @return string 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 = strtolower($type);
|
||||
|
||||
if ($type == 'sha1' || $type == null) {
|
||||
if (function_exists('sha1')) {
|
||||
$return = sha1($string);
|
||||
return $return;
|
||||
}
|
||||
$type = 'sha256';
|
||||
}
|
||||
|
||||
if ($type == 'sha256' && function_exists('mhash')) {
|
||||
return bin2hex(mhash(MHASH_SHA256, $string));
|
||||
}
|
||||
|
||||
if (function_exists('hash')) {
|
||||
return hash($type, $string);
|
||||
}
|
||||
return md5($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
* @return void
|
||||
* @see Security::hash()
|
||||
*/
|
||||
public static function setHash($hash) {
|
||||
self::$hashType = $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts/Decrypts a text using the given key.
|
||||
*
|
||||
* @param string $text Encrypted string to decrypt, normal string to encrypt
|
||||
* @param string $key Key to use
|
||||
* @return string Encrypted/Decrypted string
|
||||
*/
|
||||
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);
|
||||
return '';
|
||||
}
|
||||
|
||||
srand(Configure::read('Security.cipherSeed'));
|
||||
$out = '';
|
||||
$keyLength = strlen($key);
|
||||
for ($i = 0, $textLength = strlen($text); $i < $textLength; $i++) {
|
||||
$j = ord(substr($key, $i % $keyLength, 1));
|
||||
while ($j--) {
|
||||
rand(0, 255);
|
||||
}
|
||||
$mask = rand(0, 255);
|
||||
$out .= chr(ord(substr($text, $i, 1)) ^ $mask);
|
||||
}
|
||||
srand();
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts/Decrypts a text using the given key using rijndael method.
|
||||
*
|
||||
* @param string $text Encrypted string to decrypt, normal string to encrypt
|
||||
* @param string $key Key to use
|
||||
* @param string $operation Operation to perform, encrypt or decrypt
|
||||
* @return string Encrypted/Descrypted 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);
|
||||
return '';
|
||||
}
|
||||
if (empty($operation) || !in_array($operation, array('encrypt', 'decrypt'))) {
|
||||
trigger_error(__d('cake_dev', 'You must specify the operation for Security::rijndael(), either encrypt or decrypt'), E_USER_WARNING);
|
||||
return '';
|
||||
}
|
||||
if (strlen($key) < 32) {
|
||||
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';
|
||||
$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");
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
}
|
||||
1111
lib/Cake/Utility/Set.php
Normal file
1111
lib/Cake/Utility/Set.php
Normal file
File diff suppressed because it is too large
Load diff
605
lib/Cake/Utility/String.php
Normal file
605
lib/Cake/Utility/String.php
Normal file
|
|
@ -0,0 +1,605 @@
|
|||
<?php
|
||||
/**
|
||||
* String handling methods.
|
||||
*
|
||||
* PHP 5
|
||||
*
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright 2005-2012, 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)
|
||||
*/
|
||||
|
||||
/**
|
||||
* String handling methods.
|
||||
*
|
||||
*
|
||||
* @package Cake.Utility
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
956
lib/Cake/Utility/Validation.php
Normal file
956
lib/Cake/Utility/Validation.php
Normal file
|
|
@ -0,0 +1,956 @@
|
|||
<?php
|
||||
/**
|
||||
* Validation Class. Used for validation of model data
|
||||
*
|
||||
* PHP Version 5.x
|
||||
*
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @package Cake.Utility
|
||||
* @since CakePHP(tm) v 1.2.0.3830
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
|
||||
App::uses('Multibyte', 'I18n');
|
||||
App::uses('File', 'Utility');
|
||||
// Load multibyte if the extension is missing.
|
||||
if (!function_exists('mb_strlen')) {
|
||||
class_exists('Multibyte');
|
||||
}
|
||||
|
||||
/**
|
||||
* Offers different validation methods.
|
||||
*
|
||||
* @package Cake.Utility
|
||||
* @since CakePHP v 1.2.0.3830
|
||||
*/
|
||||
class Validation {
|
||||
|
||||
/**
|
||||
* Some complex patterns needed in multiple places
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_pattern = array(
|
||||
'hostname' => '(?:[-_a-z0-9][-_a-z0-9]*\.)*(?:[a-z0-9][-a-z0-9]{0,62})\.(?:(?:[a-z]{2}\.)?[a-z]{2,})'
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds an array of errors messages set in this class.
|
||||
* These are used for debugging purposes
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $errors = array();
|
||||
|
||||
/**
|
||||
* Checks that a string contains something other than whitespace
|
||||
*
|
||||
* Returns true if string contains something other than whitespace
|
||||
*
|
||||
* $check can be passed as an array:
|
||||
* array('check' => 'valueToCheck');
|
||||
*
|
||||
* @param string|array $check Value to check
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function notEmpty($check) {
|
||||
if (is_array($check)) {
|
||||
extract(self::_defaults($check));
|
||||
}
|
||||
|
||||
if (empty($check) && $check != '0') {
|
||||
return false;
|
||||
}
|
||||
return self::_check($check, '/[^\s]+/m');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a string contains only integer or letters
|
||||
*
|
||||
* Returns true if string contains only integer or letters
|
||||
*
|
||||
* $check can be passed as an array:
|
||||
* array('check' => 'valueToCheck');
|
||||
*
|
||||
* @param string|array $check Value to check
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function alphaNumeric($check) {
|
||||
if (is_array($check)) {
|
||||
extract(self::_defaults($check));
|
||||
}
|
||||
|
||||
if (empty($check) && $check != '0') {
|
||||
return false;
|
||||
}
|
||||
return self::_check($check, '/^[\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]+$/mu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a string length is within s specified range.
|
||||
* Spaces are included in the character count.
|
||||
* Returns true is string matches value min, max, or between min and max,
|
||||
*
|
||||
* @param string $check Value to check for length
|
||||
* @param integer $min Minimum value in range (inclusive)
|
||||
* @param integer $max Maximum value in range (inclusive)
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function between($check, $min, $max) {
|
||||
$length = mb_strlen($check);
|
||||
return ($length >= $min && $length <= $max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if field is left blank -OR- only whitespace characters are present in it's value
|
||||
* Whitespace characters include Space, Tab, Carriage Return, Newline
|
||||
*
|
||||
* $check can be passed as an array:
|
||||
* array('check' => 'valueToCheck');
|
||||
*
|
||||
* @param string|array $check Value to check
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function blank($check) {
|
||||
if (is_array($check)) {
|
||||
extract(self::_defaults($check));
|
||||
}
|
||||
return !self::_check($check, '/[^\\s]/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation of credit card numbers.
|
||||
* Returns true if $check is in the proper credit card format.
|
||||
*
|
||||
* @param string|array $check credit card number to validate
|
||||
* @param string|array $type 'all' may be passed as a sting, defaults to fast which checks format of most major credit cards
|
||||
* if an array is used only the values of the array are checked.
|
||||
* Example: array('amex', 'bankcard', 'maestro')
|
||||
* @param boolean $deep set to true this will check the Luhn algorithm of the credit card.
|
||||
* @param string $regex A custom regex can also be passed, this will be used instead of the defined regex values
|
||||
* @return boolean Success
|
||||
* @see Validation::luhn()
|
||||
*/
|
||||
public static function cc($check, $type = 'fast', $deep = false, $regex = null) {
|
||||
if (is_array($check)) {
|
||||
extract(self::_defaults($check));
|
||||
}
|
||||
|
||||
$check = str_replace(array('-', ' '), '', $check);
|
||||
if (mb_strlen($check) < 13) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_null($regex)) {
|
||||
if (self::_check($check, $regex)) {
|
||||
return self::luhn($check, $deep);
|
||||
}
|
||||
}
|
||||
$cards = array(
|
||||
'all' => array(
|
||||
'amex' => '/^3[4|7]\\d{13}$/',
|
||||
'bankcard' => '/^56(10\\d\\d|022[1-5])\\d{10}$/',
|
||||
'diners' => '/^(?:3(0[0-5]|[68]\\d)\\d{11})|(?:5[1-5]\\d{14})$/',
|
||||
'disc' => '/^(?:6011|650\\d)\\d{12}$/',
|
||||
'electron' => '/^(?:417500|4917\\d{2}|4913\\d{2})\\d{10}$/',
|
||||
'enroute' => '/^2(?:014|149)\\d{11}$/',
|
||||
'jcb' => '/^(3\\d{4}|2100|1800)\\d{11}$/',
|
||||
'maestro' => '/^(?:5020|6\\d{3})\\d{12}$/',
|
||||
'mc' => '/^5[1-5]\\d{14}$/',
|
||||
'solo' => '/^(6334[5-9][0-9]|6767[0-9]{2})\\d{10}(\\d{2,3})?$/',
|
||||
'switch' => '/^(?:49(03(0[2-9]|3[5-9])|11(0[1-2]|7[4-9]|8[1-2])|36[0-9]{2})\\d{10}(\\d{2,3})?)|(?:564182\\d{10}(\\d{2,3})?)|(6(3(33[0-4][0-9])|759[0-9]{2})\\d{10}(\\d{2,3})?)$/',
|
||||
'visa' => '/^4\\d{12}(\\d{3})?$/',
|
||||
'voyager' => '/^8699[0-9]{11}$/'
|
||||
),
|
||||
'fast' => '/^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6011[0-9]{12}|3(?:0[0-5]|[68][0-9])[0-9]{11}|3[47][0-9]{13})$/'
|
||||
);
|
||||
|
||||
if (is_array($type)) {
|
||||
foreach ($type as $value) {
|
||||
$regex = $cards['all'][strtolower($value)];
|
||||
|
||||
if (self::_check($check, $regex)) {
|
||||
return self::luhn($check, $deep);
|
||||
}
|
||||
}
|
||||
} elseif ($type == 'all') {
|
||||
foreach ($cards['all'] as $value) {
|
||||
$regex = $value;
|
||||
|
||||
if (self::_check($check, $regex)) {
|
||||
return self::luhn($check, $deep);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$regex = $cards['fast'];
|
||||
|
||||
if (self::_check($check, $regex)) {
|
||||
return self::luhn($check, $deep);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to compare 2 numeric values.
|
||||
*
|
||||
* @param string|array $check1 if string is passed for a string must also be passed for $check2
|
||||
* used as an array it must be passed as array('check1' => value, 'operator' => 'value', 'check2' -> value)
|
||||
* @param string $operator Can be either a word or operand
|
||||
* is greater >, is less <, greater or equal >=
|
||||
* less or equal <=, is less <, equal to ==, not equal !=
|
||||
* @param integer $check2 only needed if $check1 is a string
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function comparison($check1, $operator = null, $check2 = null) {
|
||||
if (is_array($check1)) {
|
||||
extract($check1, EXTR_OVERWRITE);
|
||||
}
|
||||
$operator = str_replace(array(' ', "\t", "\n", "\r", "\0", "\x0B"), '', strtolower($operator));
|
||||
|
||||
switch ($operator) {
|
||||
case 'isgreater':
|
||||
case '>':
|
||||
if ($check1 > $check2) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case 'isless':
|
||||
case '<':
|
||||
if ($check1 < $check2) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case 'greaterorequal':
|
||||
case '>=':
|
||||
if ($check1 >= $check2) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case 'lessorequal':
|
||||
case '<=':
|
||||
if ($check1 <= $check2) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case 'equalto':
|
||||
case '==':
|
||||
if ($check1 == $check2) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case 'notequal':
|
||||
case '!=':
|
||||
if ($check1 != $check2) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
self::$errors[] = __d('cake_dev', 'You must define the $operator parameter for Validation::comparison()');
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when a custom regular expression is needed.
|
||||
*
|
||||
* @param string|array $check When used as a string, $regex must also be a valid regular expression.
|
||||
* As and array: array('check' => value, 'regex' => 'valid regular expression')
|
||||
* @param string $regex If $check is passed as a string, $regex must also be set to valid regular expression
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function custom($check, $regex = null) {
|
||||
if (is_array($check)) {
|
||||
extract(self::_defaults($check));
|
||||
}
|
||||
if ($regex === null) {
|
||||
self::$errors[] = __d('cake_dev', 'You must define a regular expression for Validation::custom()');
|
||||
return false;
|
||||
}
|
||||
return self::_check($check, $regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Date validation, determines if the string passed is a valid date.
|
||||
* keys that expect full month, day and year will validate leap years
|
||||
*
|
||||
* @param string $check a valid date string
|
||||
* @param string|array $format Use a string or an array of the keys below. Arrays should be passed as array('dmy', 'mdy', etc)
|
||||
* Keys: dmy 27-12-2006 or 27-12-06 separators can be a space, period, dash, forward slash
|
||||
* mdy 12-27-2006 or 12-27-06 separators can be a space, period, dash, forward slash
|
||||
* ymd 2006-12-27 or 06-12-27 separators can be a space, period, dash, forward slash
|
||||
* dMy 27 December 2006 or 27 Dec 2006
|
||||
* Mdy December 27, 2006 or Dec 27, 2006 comma is optional
|
||||
* My December 2006 or Dec 2006
|
||||
* my 12/2006 separators can be a space, period, dash, forward slash
|
||||
* @param string $regex If a custom regular expression is used this is the only validation that will occur.
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function date($check, $format = 'ymd', $regex = null) {
|
||||
if (!is_null($regex)) {
|
||||
return self::_check($check, $regex);
|
||||
}
|
||||
|
||||
$regex['dmy'] = '%^(?:(?:31(\\/|-|\\.|\\x20)(?:0?[13578]|1[02]))\\1|(?:(?:29|30)(\\/|-|\\.|\\x20)(?:0?[1,3-9]|1[0-2])\\2))(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$|^(?:29(\\/|-|\\.|\\x20)0?2\\3(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\\d|2[0-8])(\\/|-|\\.|\\x20)(?:(?:0?[1-9])|(?:1[0-2]))\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$%';
|
||||
$regex['mdy'] = '%^(?:(?:(?:0?[13578]|1[02])(\\/|-|\\.|\\x20)31)\\1|(?:(?:0?[13-9]|1[0-2])(\\/|-|\\.|\\x20)(?:29|30)\\2))(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$|^(?:0?2(\\/|-|\\.|\\x20)29\\3(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\\/|-|\\.|\\x20)(?:0?[1-9]|1\\d|2[0-8])\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$%';
|
||||
$regex['ymd'] = '%^(?:(?:(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))(\\/|-|\\.|\\x20)(?:0?2\\1(?:29)))|(?:(?:(?:1[6-9]|[2-9]\\d)?\\d{2})(\\/|-|\\.|\\x20)(?:(?:(?:0?[13578]|1[02])\\2(?:31))|(?:(?:0?[1,3-9]|1[0-2])\\2(29|30))|(?:(?:0?[1-9])|(?:1[0-2]))\\2(?:0?[1-9]|1\\d|2[0-8]))))$%';
|
||||
$regex['dMy'] = '/^((31(?!\\ (Feb(ruary)?|Apr(il)?|June?|(Sep(?=\\b|t)t?|Nov)(ember)?)))|((30|29)(?!\\ Feb(ruary)?))|(29(?=\\ Feb(ruary)?\\ (((1[6-9]|[2-9]\\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00)))))|(0?[1-9])|1\\d|2[0-8])\\ (Jan(uary)?|Feb(ruary)?|Ma(r(ch)?|y)|Apr(il)?|Ju((ly?)|(ne?))|Aug(ust)?|Oct(ober)?|(Sep(?=\\b|t)t?|Nov|Dec)(ember)?)\\ ((1[6-9]|[2-9]\\d)\\d{2})$/';
|
||||
$regex['Mdy'] = '/^(?:(((Jan(uary)?|Ma(r(ch)?|y)|Jul(y)?|Aug(ust)?|Oct(ober)?|Dec(ember)?)\\ 31)|((Jan(uary)?|Ma(r(ch)?|y)|Apr(il)?|Ju((ly?)|(ne?))|Aug(ust)?|Oct(ober)?|(Sep)(tember)?|(Nov|Dec)(ember)?)\\ (0?[1-9]|([12]\\d)|30))|(Feb(ruary)?\\ (0?[1-9]|1\\d|2[0-8]|(29(?=,?\\ ((1[6-9]|[2-9]\\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00)))))))\\,?\\ ((1[6-9]|[2-9]\\d)\\d{2}))$/';
|
||||
$regex['My'] = '%^(Jan(uary)?|Feb(ruary)?|Ma(r(ch)?|y)|Apr(il)?|Ju((ly?)|(ne?))|Aug(ust)?|Oct(ober)?|(Sep(?=\\b|t)t?|Nov|Dec)(ember)?)[ /]((1[6-9]|[2-9]\\d)\\d{2})$%';
|
||||
$regex['my'] = '%^(((0[123456789]|10|11|12)([- /.])(([1][9][0-9][0-9])|([2][0-9][0-9][0-9]))))$%';
|
||||
|
||||
$format = (is_array($format)) ? array_values($format) : array($format);
|
||||
foreach ($format as $key) {
|
||||
if (self::_check($check, $regex[$key]) === true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a datetime value
|
||||
* All values matching the "date" core validation rule, and the "time" one will be valid
|
||||
*
|
||||
* @param array $check Value to check
|
||||
* @param string|array $dateFormat Format of the date part
|
||||
* Use a string or an array of the keys below. Arrays should be passed as array('dmy', 'mdy', etc)
|
||||
* ## Keys:
|
||||
*
|
||||
* - dmy 27-12-2006 or 27-12-06 separators can be a space, period, dash, forward slash
|
||||
* - mdy 12-27-2006 or 12-27-06 separators can be a space, period, dash, forward slash
|
||||
* - ymd 2006-12-27 or 06-12-27 separators can be a space, period, dash, forward slash
|
||||
* - dMy 27 December 2006 or 27 Dec 2006
|
||||
* - Mdy December 27, 2006 or Dec 27, 2006 comma is optional
|
||||
* - My December 2006 or Dec 2006
|
||||
* - my 12/2006 separators can be a space, period, dash, forward slash
|
||||
* @param string $regex Regex for the date part. If a custom regular expression is used this is the only validation that will occur.
|
||||
* @return boolean True if the value is valid, false otherwise
|
||||
* @see Validation::date
|
||||
* @see Validation::time
|
||||
*/
|
||||
public static function datetime($check, $dateFormat = 'ymd', $regex = null) {
|
||||
$valid = false;
|
||||
$parts = explode(' ', $check);
|
||||
if (!empty($parts) && count($parts) > 1) {
|
||||
$time = array_pop($parts);
|
||||
$date = implode(' ', $parts);
|
||||
$valid = self::date($date, $dateFormat, $regex) && self::time($time);
|
||||
}
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time validation, determines if the string passed is a valid time.
|
||||
* Validates time as 24hr (HH:MM) or am/pm ([H]H:MM[a|p]m)
|
||||
* Does not allow/validate seconds.
|
||||
*
|
||||
* @param string $check a valid time string
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function time($check) {
|
||||
return self::_check($check, '%^((0?[1-9]|1[012])(:[0-5]\d){0,2} ?([AP]M|[ap]m))$|^([01]\d|2[0-3])(:[0-5]\d){0,2}$%');
|
||||
}
|
||||
|
||||
/**
|
||||
* Boolean validation, determines if value passed is a boolean integer or true/false.
|
||||
*
|
||||
* @param string $check a valid boolean
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function boolean($check) {
|
||||
$booleanList = array(0, 1, '0', '1', true, false);
|
||||
return in_array($check, $booleanList, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a value is a valid decimal. Both the sign and exponent are optional.
|
||||
*
|
||||
* Valid Places:
|
||||
*
|
||||
* - null => Any number of decimal places, including none. The '.' is not required.
|
||||
* - true => Any number of decimal places greater than 0, or a float|double. The '.' is required.
|
||||
* - 1..N => Exactly that many number of decimal places. The '.' is required.
|
||||
*
|
||||
* @param integer $check The value the test for decimal
|
||||
* @param integer $places
|
||||
* @param string $regex If a custom regular expression is used, this is the only validation that will occur.
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function decimal($check, $places = null, $regex = null) {
|
||||
if (is_null($regex)) {
|
||||
$lnum = '[0-9]+';
|
||||
$dnum = "[0-9]*[\.]{$lnum}";
|
||||
$sign = '[+-]?';
|
||||
$exp = "(?:[eE]{$sign}{$lnum})?";
|
||||
|
||||
if ($places === null) {
|
||||
$regex = "/^{$sign}(?:{$lnum}|{$dnum}){$exp}$/";
|
||||
|
||||
} elseif ($places === true) {
|
||||
if (is_float($check) && floor($check) === $check) {
|
||||
$check = sprintf("%.1f", $check);
|
||||
}
|
||||
$regex = "/^{$sign}{$dnum}{$exp}$/";
|
||||
|
||||
} elseif (is_numeric($places)) {
|
||||
$places = '[0-9]{' . $places . '}';
|
||||
$dnum = "(?:[0-9]*[\.]{$places}|{$lnum}[\.]{$places})";
|
||||
$regex = "/^{$sign}{$dnum}{$exp}$/";
|
||||
}
|
||||
}
|
||||
return self::_check($check, $regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates for an email address.
|
||||
*
|
||||
* Only uses getmxrr() checking for deep validation if PHP 5.3.0+ is used, or
|
||||
* any PHP version on a non-windows distribution
|
||||
*
|
||||
* @param string $check Value to check
|
||||
* @param boolean $deep Perform a deeper validation (if true), by also checking availability of host
|
||||
* @param string $regex Regex to use (if none it will use built in regex)
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function email($check, $deep = false, $regex = null) {
|
||||
if (is_array($check)) {
|
||||
extract(self::_defaults($check));
|
||||
}
|
||||
|
||||
if (is_null($regex)) {
|
||||
$regex = '/^[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+)*@' . self::$_pattern['hostname'] . '$/i';
|
||||
}
|
||||
$return = self::_check($check, $regex);
|
||||
if ($deep === false || $deep === null) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
if ($return === true && preg_match('/@(' . self::$_pattern['hostname'] . ')$/i', $check, $regs)) {
|
||||
if (function_exists('getmxrr') && getmxrr($regs[1], $mxhosts)) {
|
||||
return true;
|
||||
}
|
||||
if (function_exists('checkdnsrr') && checkdnsrr($regs[1], 'MX')) {
|
||||
return true;
|
||||
}
|
||||
return is_array(gethostbynamel($regs[1]));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that value is exactly $comparedTo.
|
||||
*
|
||||
* @param mixed $check Value to check
|
||||
* @param mixed $comparedTo Value to compare
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function equalTo($check, $comparedTo) {
|
||||
return ($check === $comparedTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that value has a valid file extension.
|
||||
*
|
||||
* @param string|array $check Value to check
|
||||
* @param array $extensions file extensions to allow. By default extensions are 'gif', 'jpeg', 'png', 'jpg'
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function extension($check, $extensions = array('gif', 'jpeg', 'png', 'jpg')) {
|
||||
if (is_array($check)) {
|
||||
return self::extension(array_shift($check), $extensions);
|
||||
}
|
||||
$extension = strtolower(pathinfo($check, PATHINFO_EXTENSION));
|
||||
foreach ($extensions as $value) {
|
||||
if ($extension === strtolower($value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation of an IP address.
|
||||
*
|
||||
* @param string $check The string to test.
|
||||
* @param string $type The IP Protocol version to validate against
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function ip($check, $type = 'both') {
|
||||
$type = strtolower($type);
|
||||
$flags = 0;
|
||||
if ($type === 'ipv4') {
|
||||
$flags = FILTER_FLAG_IPV4;
|
||||
}
|
||||
if ($type === 'ipv6') {
|
||||
$flags = FILTER_FLAG_IPV6;
|
||||
}
|
||||
return (boolean)filter_var($check, FILTER_VALIDATE_IP, array('flags' => $flags));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the length of a string is greater or equal to a minimal length.
|
||||
*
|
||||
* @param string $check The string to test
|
||||
* @param integer $min The minimal string length
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function minLength($check, $min) {
|
||||
return mb_strlen($check) >= $min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the length of a string is smaller or equal to a maximal length..
|
||||
*
|
||||
* @param string $check The string to test
|
||||
* @param integer $max The maximal string length
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function maxLength($check, $max) {
|
||||
return mb_strlen($check) <= $max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a value is a monetary amount.
|
||||
*
|
||||
* @param string $check Value to check
|
||||
* @param string $symbolPosition Where symbol is located (left/right)
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function money($check, $symbolPosition = 'left') {
|
||||
$money = '(?!0,?\d)(?:\d{1,3}(?:([, .])\d{3})?(?:\1\d{3})*|(?:\d+))((?!\1)[,.]\d{2})?';
|
||||
if ($symbolPosition == 'right') {
|
||||
$regex = '/^' . $money . '(?<!\x{00a2})\p{Sc}?$/u';
|
||||
} else {
|
||||
$regex = '/^(?!\x{00a2})\p{Sc}?' . $money . '$/u';
|
||||
}
|
||||
return self::_check($check, $regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a multiple select.
|
||||
*
|
||||
* Valid Options
|
||||
*
|
||||
* - in => provide a list of choices that selections must be made from
|
||||
* - max => maximum number of non-zero choices that can be made
|
||||
* - min => minimum number of non-zero choices that can be made
|
||||
*
|
||||
* @param array $check Value to check
|
||||
* @param array $options Options for the check.
|
||||
* @param boolean $strict Defaults to true, set to false to disable strict type check
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function multiple($check, $options = array(), $strict = true) {
|
||||
$defaults = array('in' => null, 'max' => null, 'min' => null);
|
||||
$options = array_merge($defaults, $options);
|
||||
$check = array_filter((array)$check);
|
||||
if (empty($check)) {
|
||||
return false;
|
||||
}
|
||||
if ($options['max'] && count($check) > $options['max']) {
|
||||
return false;
|
||||
}
|
||||
if ($options['min'] && count($check) < $options['min']) {
|
||||
return false;
|
||||
}
|
||||
if ($options['in'] && is_array($options['in'])) {
|
||||
foreach ($check as $val) {
|
||||
if (!in_array($val, $options['in'], $strict)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a value is numeric.
|
||||
*
|
||||
* @param string $check Value to check
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function numeric($check) {
|
||||
return is_numeric($check);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a value is a natural number.
|
||||
*
|
||||
* @param string $check Value to check
|
||||
* @param boolean $allowZero Set true to allow zero, defaults to false
|
||||
* @return boolean Success
|
||||
* @see http://en.wikipedia.org/wiki/Natural_number
|
||||
*/
|
||||
public static function naturalNumber($check, $allowZero = false) {
|
||||
$regex = $allowZero ? '/^(?:0|[1-9][0-9]*)$/' : '/^[1-9][0-9]*$/';
|
||||
return self::_check($check, $regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a value is a valid phone number.
|
||||
*
|
||||
* @param string|array $check Value to check (string or array)
|
||||
* @param string $regex Regular expression to use
|
||||
* @param string $country Country code (defaults to 'all')
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function phone($check, $regex = null, $country = 'all') {
|
||||
if (is_array($check)) {
|
||||
extract(self::_defaults($check));
|
||||
}
|
||||
|
||||
if (is_null($regex)) {
|
||||
switch ($country) {
|
||||
case 'us':
|
||||
case 'all':
|
||||
case 'can':
|
||||
// includes all NANPA members.
|
||||
// see http://en.wikipedia.org/wiki/North_American_Numbering_Plan#List_of_NANPA_countries_and_territories
|
||||
$regex = '/^(?:\+?1)?[-. ]?\\(?[2-9][0-8][0-9]\\)?[-. ]?[2-9][0-9]{2}[-. ]?[0-9]{4}$/';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($regex)) {
|
||||
return self::_pass('phone', $check, $country);
|
||||
}
|
||||
return self::_check($check, $regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a given value is a valid postal code.
|
||||
*
|
||||
* @param string|array $check Value to check
|
||||
* @param string $regex Regular expression to use
|
||||
* @param string $country Country to use for formatting
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function postal($check, $regex = null, $country = 'us') {
|
||||
if (is_array($check)) {
|
||||
extract(self::_defaults($check));
|
||||
}
|
||||
|
||||
if (is_null($regex)) {
|
||||
switch ($country) {
|
||||
case 'uk':
|
||||
$regex = '/\\A\\b[A-Z]{1,2}[0-9][A-Z0-9]? [0-9][ABD-HJLNP-UW-Z]{2}\\b\\z/i';
|
||||
break;
|
||||
case 'ca':
|
||||
$regex = '/\\A\\b[ABCEGHJKLMNPRSTVXY][0-9][A-Z] [0-9][A-Z][0-9]\\b\\z/i';
|
||||
break;
|
||||
case 'it':
|
||||
case 'de':
|
||||
$regex = '/^[0-9]{5}$/i';
|
||||
break;
|
||||
case 'be':
|
||||
$regex = '/^[1-9]{1}[0-9]{3}$/i';
|
||||
break;
|
||||
case 'us':
|
||||
$regex = '/\\A\\b[0-9]{5}(?:-[0-9]{4})?\\b\\z/i';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($regex)) {
|
||||
return self::_pass('postal', $check, $country);
|
||||
}
|
||||
return self::_check($check, $regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a number is in specified range.
|
||||
* if $lower and $upper are not set, will return true if
|
||||
* $check is a legal finite on this platform
|
||||
*
|
||||
* @param string $check Value to check
|
||||
* @param integer $lower Lower limit
|
||||
* @param integer $upper Upper limit
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function range($check, $lower = null, $upper = null) {
|
||||
if (!is_numeric($check)) {
|
||||
return false;
|
||||
}
|
||||
if (isset($lower) && isset($upper)) {
|
||||
return ($check > $lower && $check < $upper);
|
||||
}
|
||||
return is_finite($check);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a value is a valid Social Security Number.
|
||||
*
|
||||
* @param string|array $check Value to check
|
||||
* @param string $regex Regular expression to use
|
||||
* @param string $country Country
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function ssn($check, $regex = null, $country = null) {
|
||||
if (is_array($check)) {
|
||||
extract(self::_defaults($check));
|
||||
}
|
||||
|
||||
if (is_null($regex)) {
|
||||
switch ($country) {
|
||||
case 'dk':
|
||||
$regex = '/\\A\\b[0-9]{6}-[0-9]{4}\\b\\z/i';
|
||||
break;
|
||||
case 'nl':
|
||||
$regex = '/\\A\\b[0-9]{9}\\b\\z/i';
|
||||
break;
|
||||
case 'us':
|
||||
$regex = '/\\A\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b\\z/i';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($regex)) {
|
||||
return self::_pass('ssn', $check, $country);
|
||||
}
|
||||
return self::_check($check, $regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a value is a valid URL according to http://www.w3.org/Addressing/URL/url-spec.txt
|
||||
*
|
||||
* The regex checks for the following component parts:
|
||||
*
|
||||
* - a valid, optional, scheme
|
||||
* - a valid ip address OR
|
||||
* a valid domain name as defined by section 2.3.1 of http://www.ietf.org/rfc/rfc1035.txt
|
||||
* with an optional port number
|
||||
* - an optional valid path
|
||||
* - an optional query string (get parameters)
|
||||
* - an optional fragment (anchor tag)
|
||||
*
|
||||
* @param string $check Value to check
|
||||
* @param boolean $strict Require URL to be prefixed by a valid scheme (one of http(s)/ftp(s)/file/news/gopher)
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function url($check, $strict = false) {
|
||||
self::_populateIp();
|
||||
$validChars = '([' . preg_quote('!"$&\'()*+,-.@_:;=~[]') . '\/0-9a-z\p{L}\p{N}]|(%[0-9a-f]{2}))';
|
||||
$regex = '/^(?:(?:https?|ftps?|sftp|file|news|gopher):\/\/)' . (!empty($strict) ? '' : '?') .
|
||||
'(?:' . self::$_pattern['IPv4'] . '|\[' . self::$_pattern['IPv6'] . '\]|' . self::$_pattern['hostname'] . ')(?::[1-9][0-9]{0,4})?' .
|
||||
'(?:\/?|\/' . $validChars . '*)?' .
|
||||
'(?:\?' . $validChars . '*)?' .
|
||||
'(?:#' . $validChars . '*)?$/iu';
|
||||
return self::_check($check, $regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a value is in a given list.
|
||||
*
|
||||
* @param string $check Value to check
|
||||
* @param array $list List to check against
|
||||
* @param boolean $strict Defaults to true, set to false to disable strict type check
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function inList($check, $list, $strict = true) {
|
||||
return in_array($check, $list, $strict);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an user-defined validation.
|
||||
*
|
||||
* @param string|array $check value that will be validated in user-defined methods.
|
||||
* @param object $object class that holds validation method
|
||||
* @param string $method class method name for validation to run
|
||||
* @param array $args arguments to send to method
|
||||
* @return mixed user-defined class class method returns
|
||||
*/
|
||||
public static function userDefined($check, $object, $method, $args = null) {
|
||||
return call_user_func_array(array($object, $method), array($check, $args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a value is a valid uuid - http://tools.ietf.org/html/rfc4122
|
||||
*
|
||||
* @param string $check Value to check
|
||||
* @return boolean Success
|
||||
*/
|
||||
public static function uuid($check) {
|
||||
$regex = '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i';
|
||||
return self::_check($check, $regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to pass unhandled Validation locales to a class starting with $classPrefix
|
||||
* and ending with Validation. For example $classPrefix = 'nl', the class would be
|
||||
* `NlValidation`.
|
||||
*
|
||||
* @param string $method The method to call on the other class.
|
||||
* @param mixed $check The value to check or an array of parameters for the method to be called.
|
||||
* @param string $classPrefix The prefix for the class to do the validation.
|
||||
* @return mixed Return of Passed method, false on failure
|
||||
*/
|
||||
protected static function _pass($method, $check, $classPrefix) {
|
||||
$className = ucwords($classPrefix) . 'Validation';
|
||||
if (!class_exists($className)) {
|
||||
trigger_error(__d('cake_dev', 'Could not find %s class, unable to complete validation.', $className), E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
if (!method_exists($className, $method)) {
|
||||
trigger_error(__d('cake_dev', 'Method %s does not exist on %s unable to complete validation.', $method, $className), E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
$check = (array)$check;
|
||||
return call_user_func_array(array($className, $method), $check);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a regular expression match.
|
||||
*
|
||||
* @param string $check Value to check against the $regex expression
|
||||
* @param string $regex Regular expression
|
||||
* @return boolean Success of match
|
||||
*/
|
||||
protected static function _check($check, $regex) {
|
||||
if (is_string($regex) && preg_match($regex, $check)) {
|
||||
self::$errors[] = false;
|
||||
return true;
|
||||
} else {
|
||||
self::$errors[] = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the values to use when value sent to validation method is
|
||||
* an array.
|
||||
*
|
||||
* @param array $params Parameters sent to validation method
|
||||
* @return void
|
||||
*/
|
||||
protected static function _defaults($params) {
|
||||
self::_reset();
|
||||
$defaults = array(
|
||||
'check' => null,
|
||||
'regex' => null,
|
||||
'country' => null,
|
||||
'deep' => false,
|
||||
'type' => null
|
||||
);
|
||||
$params = array_merge($defaults, $params);
|
||||
if ($params['country'] !== null) {
|
||||
$params['country'] = mb_strtolower($params['country']);
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Luhn algorithm
|
||||
*
|
||||
* @param string|array $check
|
||||
* @param boolean $deep
|
||||
* @return boolean Success
|
||||
* @see http://en.wikipedia.org/wiki/Luhn_algorithm
|
||||
*/
|
||||
public static function luhn($check, $deep = false) {
|
||||
if (is_array($check)) {
|
||||
extract(self::_defaults($check));
|
||||
}
|
||||
if ($deep !== true) {
|
||||
return true;
|
||||
}
|
||||
if ($check == 0) {
|
||||
return false;
|
||||
}
|
||||
$sum = 0;
|
||||
$length = strlen($check);
|
||||
|
||||
for ($position = 1 - ($length % 2); $position < $length; $position += 2) {
|
||||
$sum += $check[$position];
|
||||
}
|
||||
|
||||
for ($position = ($length % 2); $position < $length; $position += 2) {
|
||||
$number = $check[$position] * 2;
|
||||
$sum += ($number < 10) ? $number : $number - 9;
|
||||
}
|
||||
|
||||
return ($sum % 10 == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the mime type of a file
|
||||
*
|
||||
* @param string|array $check
|
||||
* @param array $mimeTypes to check for
|
||||
* @return boolean Success
|
||||
* @throws CakeException when mime type can not be determined.
|
||||
*/
|
||||
public static function mimeType($check, $mimeTypes = array()) {
|
||||
if (is_array($check) && isset($check['tmp_name'])) {
|
||||
$check = $check['tmp_name'];
|
||||
}
|
||||
|
||||
$File = new File($check);
|
||||
$mime = $File->mime();
|
||||
|
||||
if ($mime === false) {
|
||||
throw new CakeException(__d('cake_dev', 'Can not determine the mimetype.'));
|
||||
}
|
||||
return in_array($mime, $mimeTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking for upload errors
|
||||
*
|
||||
* @param string|array $check
|
||||
* @retrun boolean
|
||||
* @see http://www.php.net/manual/en/features.file-upload.errors.php
|
||||
*/
|
||||
public static function uploadError($check) {
|
||||
if (is_array($check) && isset($check['error'])) {
|
||||
$check = $check['error'];
|
||||
}
|
||||
|
||||
return $check === UPLOAD_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily populate the IP address patterns used for validations
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function _populateIp() {
|
||||
if (!isset(self::$_pattern['IPv6'])) {
|
||||
$pattern = '((([0-9A-Fa-f]{1,4}:){7}(([0-9A-Fa-f]{1,4})|:))|(([0-9A-Fa-f]{1,4}:){6}';
|
||||
$pattern .= '(:|((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})';
|
||||
$pattern .= '|(:[0-9A-Fa-f]{1,4})))|(([0-9A-Fa-f]{1,4}:){5}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})';
|
||||
$pattern .= '(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:)';
|
||||
$pattern .= '{4}(:[0-9A-Fa-f]{1,4}){0,1}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2}))';
|
||||
$pattern .= '{3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){3}(:[0-9A-Fa-f]{1,4}){0,2}';
|
||||
$pattern .= '((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|';
|
||||
$pattern .= '((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}';
|
||||
$pattern .= '((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2}))';
|
||||
$pattern .= '{3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:)(:[0-9A-Fa-f]{1,4})';
|
||||
$pattern .= '{0,4}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)';
|
||||
$pattern .= '|((:[0-9A-Fa-f]{1,4}){1,2})))|(:(:[0-9A-Fa-f]{1,4}){0,5}((:((25[0-5]|2[0-4]';
|
||||
$pattern .= '\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4})';
|
||||
$pattern .= '{1,2})))|(((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))(%.+)?';
|
||||
|
||||
self::$_pattern['IPv6'] = $pattern;
|
||||
}
|
||||
if (!isset(self::$_pattern['IPv4'])) {
|
||||
$pattern = '(?:(?:25[0-5]|2[0-4][0-9]|(?:(?:1[0-9])?|[1-9]?)[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|(?:(?:1[0-9])?|[1-9]?)[0-9])';
|
||||
self::$_pattern['IPv4'] = $pattern;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset internal variables for another validation run.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function _reset() {
|
||||
self::$errors = array();
|
||||
}
|
||||
|
||||
}
|
||||
377
lib/Cake/Utility/Xml.php
Normal file
377
lib/Cake/Utility/Xml.php
Normal file
|
|
@ -0,0 +1,377 @@
|
|||
<?php
|
||||
/**
|
||||
* XML handling for Cake.
|
||||
*
|
||||
* 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)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright 2005-2012, 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)
|
||||
*/
|
||||
App::uses('HttpSocket', 'Network/Http');
|
||||
|
||||
/**
|
||||
* XML handling for Cake.
|
||||
*
|
||||
* The methods in these classes enable the datasources that use XML to work.
|
||||
*
|
||||
* @package Cake.Utility
|
||||
*/
|
||||
class Xml {
|
||||
|
||||
/**
|
||||
* Initialize SimpleXMLElement or DOMDocument from a given XML string, file path, URL or array.
|
||||
*
|
||||
* ### Usage:
|
||||
*
|
||||
* Building XML from a string:
|
||||
*
|
||||
* `$xml = Xml::build('<example>text</example>');`
|
||||
*
|
||||
* Building XML from string (output DOMDocument):
|
||||
*
|
||||
* `$xml = Xml::build('<example>text</example>', array('return' => 'domdocument'));`
|
||||
*
|
||||
* Building XML from a file path:
|
||||
*
|
||||
* `$xml = Xml::build('/path/to/an/xml/file.xml');`
|
||||
*
|
||||
* Building from a remote URL:
|
||||
*
|
||||
* `$xml = Xml::build('http://example.com/example.xml');`
|
||||
*
|
||||
* Building from an array:
|
||||
*
|
||||
* {{{
|
||||
* $value = array(
|
||||
* 'tags' => array(
|
||||
* 'tag' => array(
|
||||
* array(
|
||||
* 'id' => '1',
|
||||
* 'name' => 'defect'
|
||||
* ),
|
||||
* array(
|
||||
* 'id' => '2',
|
||||
* 'name' => 'enhancement'
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* );
|
||||
* $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
|
||||
* is disabled by default for security reasons.
|
||||
* - If using array as input, you can pass `options` from Xml::fromArray.
|
||||
*
|
||||
* @param string|array $input XML string, a path to a file, an URL or an array
|
||||
* @param array $options The options to use
|
||||
* @return SimpleXMLElement|DOMDocument SimpleXMLElement or DOMDocument
|
||||
* @throws XmlException
|
||||
*/
|
||||
public static function build($input, $options = array()) {
|
||||
if (!is_array($options)) {
|
||||
$options = array('return' => (string)$options);
|
||||
}
|
||||
$defaults = array(
|
||||
'return' => 'simplexml',
|
||||
'loadEntities' => false,
|
||||
);
|
||||
$options = array_merge($defaults, $options);
|
||||
|
||||
if (is_array($input) || is_object($input)) {
|
||||
return self::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()) {
|
||||
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.'));
|
||||
}
|
||||
throw new XmlException(__d('cake_dev', 'XML cannot be read.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected static function _loadXml($input, $options) {
|
||||
$hasDisable = function_exists('libxml_disable_entity_loader');
|
||||
$internalErrors = libxml_use_internal_errors(true);
|
||||
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);
|
||||
}
|
||||
if ($hasDisable && !$options['loadEntities']) {
|
||||
libxml_disable_entity_loader(false);
|
||||
}
|
||||
libxml_use_internal_errors($internalErrors);
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an array into a SimpleXMLElement
|
||||
*
|
||||
* ### Options
|
||||
*
|
||||
* - `format` If create childs ('tags') or attributes ('attribute').
|
||||
* - `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(
|
||||
* 'id' => 1,
|
||||
* 'value' => 'defect',
|
||||
* '@' => 'description'
|
||||
* )
|
||||
* )
|
||||
* );
|
||||
* }}}
|
||||
*
|
||||
* 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:
|
||||
*
|
||||
* `<root><tag id="1" value="defect">description</tag></root>`
|
||||
*
|
||||
* @param array $input Array with data
|
||||
* @param array $options The options to use
|
||||
* @return SimpleXMLElement|DOMDocument SimpleXMLElement or DOMDocument
|
||||
* @throws XmlException
|
||||
*/
|
||||
public static function fromArray($input, $options = array()) {
|
||||
if (!is_array($input) || count($input) !== 1) {
|
||||
throw new XmlException(__d('cake_dev', 'Invalid input.'));
|
||||
}
|
||||
$key = key($input);
|
||||
if (is_integer($key)) {
|
||||
throw new XmlException(__d('cake_dev', 'The key of input must be alphanumeric'));
|
||||
}
|
||||
|
||||
if (!is_array($options)) {
|
||||
$options = array('format' => (string)$options);
|
||||
}
|
||||
$defaults = array(
|
||||
'format' => 'tags',
|
||||
'version' => '1.0',
|
||||
'encoding' => Configure::read('App.encoding'),
|
||||
'return' => 'simplexml'
|
||||
);
|
||||
$options = array_merge($defaults, $options);
|
||||
|
||||
$dom = new DOMDocument($options['version'], $options['encoding']);
|
||||
self::_fromArray($dom, $dom, $input, $options['format']);
|
||||
|
||||
$options['return'] = strtolower($options['return']);
|
||||
if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
|
||||
return new SimpleXMLElement($dom->saveXML());
|
||||
}
|
||||
return $dom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive method to create childs from array
|
||||
*
|
||||
* @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.
|
||||
* @return void
|
||||
* @throws XmlException
|
||||
*/
|
||||
protected static function _fromArray($dom, $node, &$data, $format) {
|
||||
if (empty($data) || !is_array($data)) {
|
||||
return;
|
||||
}
|
||||
foreach ($data as $key => $value) {
|
||||
if (is_string($key)) {
|
||||
if (!is_array($value)) {
|
||||
if (is_bool($value)) {
|
||||
$value = (int)$value;
|
||||
} elseif ($value === null) {
|
||||
$value = '';
|
||||
}
|
||||
$isNamespace = strpos($key, 'xmlns:');
|
||||
if ($isNamespace !== false) {
|
||||
$node->setAttributeNS('http://www.w3.org/2000/xmlns/', $key, $value);
|
||||
continue;
|
||||
}
|
||||
if ($key[0] !== '@' && $format === 'tags') {
|
||||
$child = null;
|
||||
if (!is_numeric($value)) {
|
||||
// Escape special characters
|
||||
// http://www.w3.org/TR/REC-xml/#syntax
|
||||
// https://bugs.php.net/bug.php?id=36795
|
||||
$child = $dom->createElement($key, '');
|
||||
$child->appendChild(new DOMText($value));
|
||||
} else {
|
||||
$child = $dom->createElement($key, $value);
|
||||
}
|
||||
$node->appendChild($child);
|
||||
} else {
|
||||
if ($key[0] === '@') {
|
||||
$key = substr($key, 1);
|
||||
}
|
||||
$attribute = $dom->createAttribute($key);
|
||||
$attribute->appendChild($dom->createTextNode($value));
|
||||
$node->appendChild($attribute);
|
||||
}
|
||||
} else {
|
||||
if ($key[0] === '@') {
|
||||
throw new XmlException(__d('cake_dev', 'Invalid array'));
|
||||
}
|
||||
if (is_numeric(implode('', array_keys($value)))) { // List
|
||||
foreach ($value as $item) {
|
||||
$itemData = compact('dom', 'node', 'key', 'format');
|
||||
$itemData['value'] = $item;
|
||||
self::_createChild($itemData);
|
||||
}
|
||||
} else { // Struct
|
||||
self::_createChild(compact('dom', 'node', 'key', 'value', 'format'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new XmlException(__d('cake_dev', 'Invalid array'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to _fromArray(). It will create childs of arrays
|
||||
*
|
||||
* @param array $data Array with informations to create childs
|
||||
* @return void
|
||||
*/
|
||||
protected static function _createChild($data) {
|
||||
extract($data);
|
||||
$childNS = $childValue = null;
|
||||
if (is_array($value)) {
|
||||
if (isset($value['@'])) {
|
||||
$childValue = (string)$value['@'];
|
||||
unset($value['@']);
|
||||
}
|
||||
if (isset($value['xmlns:'])) {
|
||||
$childNS = $value['xmlns:'];
|
||||
unset($value['xmlns:']);
|
||||
}
|
||||
} elseif (!empty($value) || $value === 0) {
|
||||
$childValue = (string)$value;
|
||||
}
|
||||
|
||||
if ($childValue) {
|
||||
$child = $dom->createElement($key, $childValue);
|
||||
} else {
|
||||
$child = $dom->createElement($key);
|
||||
}
|
||||
if ($childNS) {
|
||||
$child->setAttribute('xmlns', $childNS);
|
||||
}
|
||||
|
||||
self::_fromArray($dom, $child, $value, $format);
|
||||
$node->appendChild($child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this XML structure as a array.
|
||||
*
|
||||
* @param SimpleXMLElement|DOMDocument|DOMNode $obj SimpleXMLElement, DOMDocument or DOMNode instance
|
||||
* @return array Array representation of the XML structure.
|
||||
* @throws XmlException
|
||||
*/
|
||||
public static function toArray($obj) {
|
||||
if ($obj instanceof DOMNode) {
|
||||
$obj = simplexml_import_dom($obj);
|
||||
}
|
||||
if (!($obj instanceof SimpleXMLElement)) {
|
||||
throw new XmlException(__d('cake_dev', 'The input is not instance of SimpleXMLElement, DOMDocument or DOMNode.'));
|
||||
}
|
||||
$result = array();
|
||||
$namespaces = array_merge(array('' => ''), $obj->getNamespaces(true));
|
||||
self::_toArray($obj, $result, '', array_keys($namespaces));
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive method to toArray
|
||||
*
|
||||
* @param SimpleXMLElement $xml SimpleXMLElement object
|
||||
* @param array $parentData Parent array with data
|
||||
* @param string $ns Namespace of current child
|
||||
* @param array $namespaces List of namespaces in XML
|
||||
* @return void
|
||||
*/
|
||||
protected static function _toArray($xml, &$parentData, $ns, $namespaces) {
|
||||
$data = array();
|
||||
|
||||
foreach ($namespaces as $namespace) {
|
||||
foreach ($xml->attributes($namespace, true) as $key => $value) {
|
||||
if (!empty($namespace)) {
|
||||
$key = $namespace . ':' . $key;
|
||||
}
|
||||
$data['@' . $key] = (string)$value;
|
||||
}
|
||||
|
||||
foreach ($xml->children($namespace, true) as $child) {
|
||||
self::_toArray($child, $data, $namespace, $namespaces);
|
||||
}
|
||||
}
|
||||
|
||||
$asString = trim((string)$xml);
|
||||
if (empty($data)) {
|
||||
$data = $asString;
|
||||
} elseif (!empty($asString)) {
|
||||
$data['@'] = $asString;
|
||||
}
|
||||
|
||||
if (!empty($ns)) {
|
||||
$ns .= ':';
|
||||
}
|
||||
$name = $ns . $xml->getName();
|
||||
if (isset($parentData[$name])) {
|
||||
if (!is_array($parentData[$name]) || !isset($parentData[$name][0])) {
|
||||
$parentData[$name] = array($parentData[$name]);
|
||||
}
|
||||
$parentData[$name][] = $data;
|
||||
} else {
|
||||
$parentData[$name] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue