This commit is contained in:
2024-11-05 12:10:06 +08:00
commit 6b687feee2
3995 changed files with 418583 additions and 0 deletions
+192
View File
@@ -0,0 +1,192 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.1.6
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Utility;
use RuntimeException;
/**
* Cookie Crypt Trait.
*
* Provides the encrypt/decrypt logic for the CookieComponent.
*
* @link https://book.cakephp.org/4/en/controllers/components/cookie.html
*/
trait CookieCryptTrait
{
/**
* Valid cipher names for encrypted cookies.
*
* @var array<string>
*/
protected $_validCiphers = ['aes'];
/**
* Returns the encryption key to be used.
*
* @return string
*/
abstract protected function _getCookieEncryptionKey(): string;
/**
* Encrypts $value using public $type method in Security class
*
* @param array|string $value Value to encrypt
* @param string|false $encrypt Encryption mode to use. False
* disabled encryption.
* @param string|null $key Used as the security salt if specified.
* @return string Encoded values
*/
protected function _encrypt($value, $encrypt, ?string $key = null): string
{
if (is_array($value)) {
$value = $this->_implode($value);
}
if ($encrypt === false) {
return $value;
}
$this->_checkCipher($encrypt);
$prefix = 'Q2FrZQ==.';
$cipher = '';
if ($key === null) {
$key = $this->_getCookieEncryptionKey();
}
if ($encrypt === 'aes') {
$cipher = Security::encrypt($value, $key);
}
return $prefix . base64_encode($cipher);
}
/**
* Helper method for validating encryption cipher names.
*
* @param string $encrypt The cipher name.
* @return void
* @throws \RuntimeException When an invalid cipher is provided.
*/
protected function _checkCipher(string $encrypt): void
{
if (!in_array($encrypt, $this->_validCiphers, true)) {
$msg = sprintf(
'Invalid encryption cipher. Must be one of %s or false.',
implode(', ', $this->_validCiphers)
);
throw new RuntimeException($msg);
}
}
/**
* Decrypts $value using public $type method in Security class
*
* @param array<string>|string $values Values to decrypt
* @param string|false $mode Encryption mode
* @param string|null $key Used as the security salt if specified.
* @return array|string Decrypted values
*/
protected function _decrypt($values, $mode, ?string $key = null)
{
if (is_string($values)) {
return $this->_decode($values, $mode, $key);
}
$decrypted = [];
foreach ($values as $name => $value) {
$decrypted[$name] = $this->_decode($value, $mode, $key);
}
return $decrypted;
}
/**
* Decodes and decrypts a single value.
*
* @param string $value The value to decode & decrypt.
* @param string|false $encrypt The encryption cipher to use.
* @param string|null $key Used as the security salt if specified.
* @return array|string Decoded values.
*/
protected function _decode(string $value, $encrypt, ?string $key)
{
if (!$encrypt) {
return $this->_explode($value);
}
$this->_checkCipher($encrypt);
$prefix = 'Q2FrZQ==.';
$prefixLength = strlen($prefix);
if (strncmp($value, $prefix, $prefixLength) !== 0) {
return '';
}
$value = base64_decode(substr($value, $prefixLength), true);
if ($value === false || $value === '') {
return '';
}
if ($key === null) {
$key = $this->_getCookieEncryptionKey();
}
if ($encrypt === 'aes') {
$value = Security::decrypt($value, $key);
}
if ($value === null) {
return '';
}
return $this->_explode($value);
}
/**
* Implode method to keep keys are multidimensional arrays
*
* @param array $array Map of key and values
* @return string A JSON encoded string.
*/
protected function _implode(array $array): string
{
return json_encode($array);
}
/**
* Explode method to return array from string set in CookieComponent::_implode()
* Maintains reading backwards compatibility with 1.x CookieComponent::_implode().
*
* @param string $string A string containing JSON encoded data, or a bare string.
* @return array|string Map of key and values
*/
protected function _explode(string $string)
{
$first = substr($string, 0, 1);
if ($first === '{' || $first === '[') {
$ret = json_decode($string, true);
return $ret ?? $string;
}
$array = [];
foreach (explode(',', $string) as $pair) {
$key = explode('|', $pair);
if (!isset($key[1])) {
return $key[0];
}
$array[$key[0]] = $key[1];
}
return $array;
}
}
+79
View File
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Utility\Crypto;
/**
* OpenSSL implementation of crypto features for Cake\Utility\Security
*
* This class is not intended to be used directly and should only
* be used in the context of {@link \Cake\Utility\Security}.
*
* @internal
*/
class OpenSsl
{
/**
* @var string
*/
protected const METHOD_AES_256_CBC = 'aes-256-cbc';
/**
* Encrypt a value using AES-256.
*
* *Caveat* You cannot properly encrypt/decrypt data with trailing null bytes.
* Any trailing null bytes will be removed on decryption due to how PHP pads messages
* with nulls prior to encryption.
*
* @param string $plain The value to encrypt.
* @param string $key The 256 bit/32 byte key to use as a cipher key.
* @return string Encrypted data.
* @throws \InvalidArgumentException On invalid data or key.
*/
public static function encrypt(string $plain, string $key): string
{
$method = static::METHOD_AES_256_CBC;
$ivSize = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($ivSize);
return $iv . openssl_encrypt($plain, $method, $key, OPENSSL_RAW_DATA, $iv);
}
/**
* Decrypt a value using AES-256.
*
* @param string $cipher The ciphertext to decrypt.
* @param string $key The 256 bit/32 byte key to use as a cipher key.
* @return string Decrypted data. Any trailing null bytes will be removed.
* @throws \InvalidArgumentException On invalid data or key.
*/
public static function decrypt(string $cipher, string $key): ?string
{
$method = static::METHOD_AES_256_CBC;
$ivSize = openssl_cipher_iv_length($method);
$iv = mb_substr($cipher, 0, $ivSize, '8bit');
$cipher = mb_substr($cipher, $ivSize, null, '8bit');
$value = openssl_decrypt($cipher, $method, $key, OPENSSL_RAW_DATA, $iv);
if ($value === false) {
return null;
}
return $value;
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Utility\Exception;
use Cake\Core\Exception\CakeException;
/**
* Exception class for Xml. This exception will be thrown from Xml when it
* encounters an error.
*/
class XmlException extends CakeException
{
}
+1274
View File
File diff suppressed because it is too large Load Diff
+524
View File
@@ -0,0 +1,524 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.2.9
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Utility;
/**
* Pluralize and singularize English words.
*
* Inflector pluralizes and singularizes English nouns.
* Used by CakePHP's naming conventions throughout the framework.
*
* @link https://book.cakephp.org/4/en/core-libraries/inflector.html
*/
class Inflector
{
/**
* Plural inflector rules
*
* @var array<string, string>
*/
protected static $_plural = [
'/(s)tatus$/i' => '\1tatuses',
'/(quiz)$/i' => '\1zes',
'/^(ox)$/i' => '\1\2en',
'/([m|l])ouse$/i' => '\1ice',
'/(matr|vert)(ix|ex)$/i' => '\1ices',
'/(x|ch|ss|sh)$/i' => '\1es',
'/([^aeiouy]|qu)y$/i' => '\1ies',
'/(hive)$/i' => '\1s',
'/(chef)$/i' => '\1s',
'/(?:([^f])fe|([lre])f)$/i' => '\1\2ves',
'/sis$/i' => 'ses',
'/([ti])um$/i' => '\1a',
'/(p)erson$/i' => '\1eople',
'/(?<!u)(m)an$/i' => '\1en',
'/(c)hild$/i' => '\1hildren',
'/(buffal|tomat)o$/i' => '\1\2oes',
'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin)us$/i' => '\1i',
'/us$/i' => 'uses',
'/(alias)$/i' => '\1es',
'/(ax|cris|test)is$/i' => '\1es',
'/s$/' => 's',
'/^$/' => '',
'/$/' => 's',
];
/**
* Singular inflector rules
*
* @var array<string, string>
*/
protected static $_singular = [
'/(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|lens)(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)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',
'/(s)pecies$/i' => '\1\2pecies',
'/([^aeiouy]|qu)ies$/i' => '\1y',
'/(tive)s$/i' => '\1',
'/(hive)s$/i' => '\1',
'/(drive)s$/i' => '\1',
'/([le])ves$/i' => '\1f',
'/([^rfoa])ves$/i' => '\1fe',
'/(^analy)ses$/i' => '\1sis',
'/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
'/([ti])a$/i' => '\1um',
'/(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' => '',
];
/**
* Irregular rules
*
* @var array<string, string>
*/
protected static $_irregular = [
'atlas' => 'atlases',
'beef' => 'beefs',
'brief' => 'briefs',
'brother' => 'brothers',
'cafe' => 'cafes',
'child' => 'children',
'cookie' => 'cookies',
'corpus' => 'corpuses',
'cow' => 'cows',
'criterion' => 'criteria',
'ganglion' => 'ganglions',
'genie' => 'genies',
'genus' => 'genera',
'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',
'potato' => 'potatoes',
'hero' => 'heroes',
'tooth' => 'teeth',
'goose' => 'geese',
'foot' => 'feet',
'foe' => 'foes',
'sieve' => 'sieves',
'cache' => 'caches',
];
/**
* Words that should not be inflected
*
* @var array<string>
*/
protected static $_uninflected = [
'.*[nrlm]ese', '.*data', '.*deer', '.*fish', '.*measles', '.*ois',
'.*pox', '.*sheep', 'people', 'feedback', 'stadia', '.*?media',
'chassis', 'clippers', 'debris', 'diabetes', 'equipment', 'gallows',
'graffiti', 'headquarters', 'information', 'innings', 'news', 'nexus',
'pokemon', 'proceedings', 'research', 'sea[- ]bass', 'series', 'species', 'weather',
];
/**
* Method cache array.
*
* @var array<string, mixed>
*/
protected static $_cache = [];
/**
* The initial state of Inflector so reset() works.
*
* @var array
*/
protected static $_initialState = [];
/**
* Cache inflected values, and return if already available
*
* @param string $type Inflection type
* @param string $key Original value
* @param string|false $value Inflected value
* @return string|false Inflected value on cache hit or false on cache miss.
*/
protected static function _cache(string $type, string $key, $value = false)
{
$key = '_' . $key;
$type = '_' . $type;
if ($value !== false) {
static::$_cache[$type][$key] = $value;
return $value;
}
if (!isset(static::$_cache[$type][$key])) {
return false;
}
return static::$_cache[$type][$key];
}
/**
* Clears Inflectors inflected value caches. And resets the inflection
* rules to the initial values.
*
* @return void
*/
public static function reset(): void
{
if (empty(static::$_initialState)) {
static::$_initialState = get_class_vars(self::class);
return;
}
foreach (static::$_initialState as $key => $val) {
if ($key !== '_initialState') {
static::${$key} = $val;
}
}
}
/**
* Adds custom inflection $rules, of either 'plural', 'singular',
* 'uninflected' or 'irregular' $type.
*
* ### Usage:
*
* ```
* Inflector::rules('plural', ['/^(inflect)or$/i' => '\1ables']);
* Inflector::rules('irregular', ['red' => 'redlings']);
* Inflector::rules('uninflected', ['dontinflectme']);
* ```
*
* @param string $type The type of inflection, either 'plural', 'singular',
* or 'uninflected'.
* @param array $rules Array of rules to be added.
* @param bool $reset If true, will unset default inflections for all
* new rules that are being defined in $rules.
* @return void
*/
public static function rules(string $type, array $rules, bool $reset = false): void
{
$var = '_' . $type;
if ($reset) {
static::${$var} = $rules;
} elseif ($type === 'uninflected') {
static::$_uninflected = array_merge(
$rules,
static::$_uninflected
);
} else {
static::${$var} = $rules + static::${$var};
}
static::$_cache = [];
}
/**
* Return $word in plural form.
*
* @param string $word Word in singular
* @return string Word in plural
* @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-plural-singular-forms
*/
public static function pluralize(string $word): string
{
if (isset(static::$_cache['pluralize'][$word])) {
return static::$_cache['pluralize'][$word];
}
if (!isset(static::$_cache['irregular']['pluralize'])) {
$words = array_keys(static::$_irregular);
static::$_cache['irregular']['pluralize'] = '/(.*?(?:\\b|_))(' . implode('|', $words) . ')$/i';
$upperWords = array_map('ucfirst', $words);
static::$_cache['irregular']['upperPluralize'] = '/(.*?(?:\\b|[a-z]))(' . implode('|', $upperWords) . ')$/';
}
if (
preg_match(static::$_cache['irregular']['pluralize'], $word, $regs) ||
preg_match(static::$_cache['irregular']['upperPluralize'], $word, $regs)
) {
static::$_cache['pluralize'][$word] = $regs[1] . substr($regs[2], 0, 1) .
substr(static::$_irregular[strtolower($regs[2])], 1);
return static::$_cache['pluralize'][$word];
}
if (!isset(static::$_cache['uninflected'])) {
static::$_cache['uninflected'] = '/^(' . implode('|', static::$_uninflected) . ')$/i';
}
if (preg_match(static::$_cache['uninflected'], $word, $regs)) {
static::$_cache['pluralize'][$word] = $word;
return $word;
}
foreach (static::$_plural as $rule => $replacement) {
if (preg_match($rule, $word)) {
static::$_cache['pluralize'][$word] = preg_replace($rule, $replacement, $word);
return static::$_cache['pluralize'][$word];
}
}
return $word;
}
/**
* Return $word in singular form.
*
* @param string $word Word in plural
* @return string Word in singular
* @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-plural-singular-forms
*/
public static function singularize(string $word): string
{
if (isset(static::$_cache['singularize'][$word])) {
return static::$_cache['singularize'][$word];
}
if (!isset(static::$_cache['irregular']['singular'])) {
$wordList = array_values(static::$_irregular);
static::$_cache['irregular']['singular'] = '/(.*?(?:\\b|_))(' . implode('|', $wordList) . ')$/i';
$upperWordList = array_map('ucfirst', $wordList);
static::$_cache['irregular']['singularUpper'] = '/(.*?(?:\\b|[a-z]))(' .
implode('|', $upperWordList) .
')$/';
}
if (
preg_match(static::$_cache['irregular']['singular'], $word, $regs) ||
preg_match(static::$_cache['irregular']['singularUpper'], $word, $regs)
) {
$suffix = array_search(strtolower($regs[2]), static::$_irregular, true);
$suffix = $suffix ? substr($suffix, 1) : '';
static::$_cache['singularize'][$word] = $regs[1] . substr($regs[2], 0, 1) . $suffix;
return static::$_cache['singularize'][$word];
}
if (!isset(static::$_cache['uninflected'])) {
static::$_cache['uninflected'] = '/^(' . implode('|', static::$_uninflected) . ')$/i';
}
if (preg_match(static::$_cache['uninflected'], $word, $regs)) {
static::$_cache['pluralize'][$word] = $word;
return $word;
}
foreach (static::$_singular as $rule => $replacement) {
if (preg_match($rule, $word)) {
static::$_cache['singularize'][$word] = preg_replace($rule, $replacement, $word);
return static::$_cache['singularize'][$word];
}
}
static::$_cache['singularize'][$word] = $word;
return $word;
}
/**
* Returns the input lower_case_delimited_string as a CamelCasedString.
*
* @param string $string String to camelize
* @param string $delimiter the delimiter in the input string
* @return string CamelizedStringLikeThis.
* @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-camelcase-and-under-scored-forms
*/
public static function camelize(string $string, string $delimiter = '_'): string
{
$cacheKey = __FUNCTION__ . $delimiter;
$result = static::_cache($cacheKey, $string);
if ($result === false) {
$result = str_replace(' ', '', static::humanize($string, $delimiter));
static::_cache($cacheKey, $string, $result);
}
return $result;
}
/**
* Returns the input CamelCasedString as an underscored_string.
*
* Also replaces dashes with underscores
*
* @param string $string CamelCasedString to be "underscorized"
* @return string underscore_version of the input string
* @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-camelcase-and-under-scored-forms
*/
public static function underscore(string $string): string
{
return static::delimit(str_replace('-', '_', $string), '_');
}
/**
* Returns the input CamelCasedString as an dashed-string.
*
* Also replaces underscores with dashes
*
* @param string $string The string to dasherize.
* @return string Dashed version of the input string
*/
public static function dasherize(string $string): string
{
return static::delimit(str_replace('_', '-', $string), '-');
}
/**
* Returns the input lower_case_delimited_string as 'A Human Readable String'.
* (Underscores are replaced by spaces and capitalized following words.)
*
* @param string $string String to be humanized
* @param string $delimiter the character to replace with a space
* @return string Human-readable string
* @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-human-readable-forms
*/
public static function humanize(string $string, string $delimiter = '_'): string
{
$cacheKey = __FUNCTION__ . $delimiter;
$result = static::_cache($cacheKey, $string);
if ($result === false) {
$result = explode(' ', str_replace($delimiter, ' ', $string));
foreach ($result as &$word) {
$word = mb_strtoupper(mb_substr($word, 0, 1)) . mb_substr($word, 1);
}
$result = implode(' ', $result);
static::_cache($cacheKey, $string, $result);
}
return $result;
}
/**
* Expects a CamelCasedInputString, and produces a lower_case_delimited_string
*
* @param string $string String to delimit
* @param string $delimiter the character to use as a delimiter
* @return string delimited string
*/
public static function delimit(string $string, string $delimiter = '_'): string
{
$cacheKey = __FUNCTION__ . $delimiter;
$result = static::_cache($cacheKey, $string);
if ($result === false) {
$result = mb_strtolower(preg_replace('/(?<=\\w)([A-Z])/', $delimiter . '\\1', $string));
static::_cache($cacheKey, $string, $result);
}
return $result;
}
/**
* Returns corresponding table name for given model $className. ("people" for the class name "Person").
*
* @param string $className Name of class to get database table name for
* @return string Name of the database table for given class
* @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-table-and-class-name-forms
*/
public static function tableize(string $className): string
{
$result = static::_cache(__FUNCTION__, $className);
if ($result === false) {
$result = static::pluralize(static::underscore($className));
static::_cache(__FUNCTION__, $className, $result);
}
return $result;
}
/**
* Returns a singular, CamelCase inflection for given database table. ("Person" for the table name "people")
*
* @param string $tableName Name of database table to get class name for
* @return string Class name
* @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-table-and-class-name-forms
*/
public static function classify(string $tableName): string
{
$result = static::_cache(__FUNCTION__, $tableName);
if ($result === false) {
$result = static::camelize(static::singularize($tableName));
static::_cache(__FUNCTION__, $tableName, $result);
}
return $result;
}
/**
* Returns camelBacked version of an underscored string.
*
* @param string $string String to convert.
* @return string in variable form
* @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-variable-names
*/
public static function variable(string $string): string
{
$result = static::_cache(__FUNCTION__, $string);
if ($result === false) {
$camelized = static::camelize(static::underscore($string));
$replace = strtolower(substr($camelized, 0, 1));
$result = $replace . substr($camelized, 1);
static::_cache(__FUNCTION__, $string, $result);
}
return $result;
}
}
+22
View File
@@ -0,0 +1,22 @@
The MIT License (MIT)
CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org)
Copyright (c) 2005-2020, Cake Software Foundation, Inc. (https://cakefoundation.org)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+118
View File
@@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Utility;
/**
* Provides features for merging object properties recursively with
* parent classes.
*/
trait MergeVariablesTrait
{
/**
* Merge the list of $properties with all parent classes of the current class.
*
* ### Options:
*
* - `associative` - A list of properties that should be treated as associative arrays.
* Properties in this list will be passed through Hash::normalize() before merging.
*
* @param array<string> $properties An array of properties and the merge strategy for them.
* @param array<string, mixed> $options The options to use when merging properties.
* @return void
*/
protected function _mergeVars(array $properties, array $options = []): void
{
$class = static::class;
$parents = [];
while (true) {
$parent = get_parent_class($class);
if (!$parent) {
break;
}
$parents[] = $parent;
$class = $parent;
}
foreach ($properties as $property) {
if (!property_exists($this, $property)) {
continue;
}
$thisValue = $this->{$property};
if ($thisValue === null || $thisValue === false) {
continue;
}
$this->_mergeProperty($property, $parents, $options);
}
}
/**
* Merge a single property with the values declared in all parent classes.
*
* @param string $property The name of the property being merged.
* @param array<string> $parentClasses An array of classes you want to merge with.
* @param array<string, mixed> $options Options for merging the property, see _mergeVars()
* @return void
*/
protected function _mergeProperty(string $property, array $parentClasses, array $options): void
{
$thisValue = $this->{$property};
$isAssoc = false;
if (
isset($options['associative']) &&
in_array($property, (array)$options['associative'], true)
) {
$isAssoc = true;
}
if ($isAssoc) {
$thisValue = Hash::normalize($thisValue);
}
foreach ($parentClasses as $class) {
$parentProperties = get_class_vars($class);
if (empty($parentProperties[$property])) {
continue;
}
$parentProperty = $parentProperties[$property];
if (!is_array($parentProperty)) {
continue;
}
$thisValue = $this->_mergePropertyData($thisValue, $parentProperty, $isAssoc);
}
$this->{$property} = $thisValue;
}
/**
* Merge each of the keys in a property together.
*
* @param array $current The current merged value.
* @param array $parent The parent class' value.
* @param bool $isAssoc Whether the merging should be done in associative mode.
* @return array The updated value.
*/
protected function _mergePropertyData(array $current, array $parent, bool $isAssoc)
{
if (!$isAssoc) {
return array_merge($parent, $current);
}
$parent = Hash::normalize($parent);
foreach ($parent as $key => $value) {
if (!isset($current[$key])) {
$current[$key] = $value;
}
}
return $current;
}
}
+91
View File
@@ -0,0 +1,91 @@
[![Total Downloads](https://img.shields.io/packagist/dt/cakephp/utility.svg?style=flat-square)](https://packagist.org/packages/cakephp/utility)
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE.txt)
# CakePHP Utility Classes
This library provides a range of utility classes that are used throughout the CakePHP framework
## What's in the toolbox?
### Hash
A ``Hash`` (as in PHP arrays) class, capable of extracting data using an intuitive DSL:
```php
$things = [
['name' => 'Mark', 'age' => 15],
['name' => 'Susan', 'age' => 30],
['name' => 'Lucy', 'age' => 25]
];
$bigPeople = Hash::extract($things, '{n}[age>21].name');
// $bigPeople will contain ['Susan', 'Lucy']
```
Check the [official Hash class documentation](https://book.cakephp.org/4/en/core-libraries/hash.html)
### Inflector
The Inflector class takes a string and can manipulate it to handle word variations
such as pluralizations or camelizing.
```php
echo Inflector::pluralize('Apple'); // echoes Apples
echo Inflector::singularize('People'); // echoes Person
```
Check the [official Inflector class documentation](https://book.cakephp.org/4/en/core-libraries/inflector.html)
### Text
The Text class includes convenience methods for creating and manipulating strings.
```php
Text::insert(
'My name is :name and I am :age years old.',
['name' => 'Bob', 'age' => '65']
);
// Returns: "My name is Bob and I am 65 years old."
$text = 'This is the song that never ends.';
$result = Text::wrap($text, 22);
// Returns
This is the song
that never ends.
```
Check the [official Text class documentation](https://book.cakephp.org/4/en/core-libraries/text.html)
### Security
The security library handles basic security measures such as providing methods for hashing and encrypting data.
```php
$key = 'wt1U5MACWJFTXGenFoZoiLwQGrLgdbHA';
$result = Security::encrypt($value, $key);
Security::decrypt($result, $key);
```
Check the [official Security class documentation](https://book.cakephp.org/4/en/core-libraries/security.html)
### Xml
The Xml class allows you to easily transform arrays into SimpleXMLElement or DOMDocument objects
and back into arrays again
```php
$data = [
'post' => [
'id' => 1,
'title' => 'Best post',
'body' => ' ... '
]
];
$xml = Xml::build($data);
```
Check the [official Xml class documentation](https://book.cakephp.org/4/en/core-libraries/xml.html)
+309
View File
@@ -0,0 +1,309 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.10.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Utility;
use Cake\Utility\Crypto\OpenSsl;
use InvalidArgumentException;
use RuntimeException;
/**
* Security Library contains utility methods related to security
*/
class Security
{
/**
* Default hash method. If `$type` param for `Security::hash()` is not specified
* this value is used. Defaults to 'sha1'.
*
* @var string
*/
public static $hashType = 'sha1';
/**
* The HMAC salt to use for encryption and decryption routines
*
* @var string|null
*/
protected static $_salt;
/**
* The crypto implementation to use.
*
* @var object|null
*/
protected static $_instance;
/**
* Create a hash from string using given method.
*
* @param string $string String to hash
* @param string|null $algorithm Hashing algo to use (i.e. sha1, sha256 etc.).
* Can be any valid algo included in list returned by hash_algos().
* If no value is passed the type specified by `Security::$hashType` is used.
* @param mixed $salt If true, automatically prepends the value returned by
* Security::getSalt() to $string.
* @return string Hash
* @throws \RuntimeException
* @link https://book.cakephp.org/4/en/core-libraries/security.html#hashing-data
*/
public static function hash(string $string, ?string $algorithm = null, $salt = false): string
{
if (empty($algorithm)) {
$algorithm = static::$hashType;
}
$algorithm = strtolower($algorithm);
$availableAlgorithms = hash_algos();
if (!in_array($algorithm, $availableAlgorithms, true)) {
throw new RuntimeException(sprintf(
'The hash type `%s` was not found. Available algorithms are: %s',
$algorithm,
implode(', ', $availableAlgorithms)
));
}
if ($salt) {
if (!is_string($salt)) {
$salt = static::getSalt();
}
$string = $salt . $string;
}
return hash($algorithm, $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 etc.)
* @return void
* @see \Cake\Utility\Security::hash()
*/
public static function setHash(string $hash): void
{
static::$hashType = $hash;
}
/**
* Get random bytes from a secure source.
*
* This method will fall back to an insecure source an trigger a warning
* if it cannot find a secure source of random data.
*
* @param int $length The number of bytes you want.
* @return string Random bytes in binary.
*/
public static function randomBytes(int $length): string
{
/** @psalm-suppress ArgumentTypeCoercion */
return random_bytes($length);
}
/**
* Creates a secure random string.
*
* @param int $length String length. Default 64.
* @return string
*/
public static function randomString(int $length = 64): string
{
return substr(
bin2hex(Security::randomBytes((int)ceil($length / 2))),
0,
$length
);
}
/**
* Like randomBytes() above, but not cryptographically secure.
*
* @param int $length The number of bytes you want.
* @return string Random bytes in binary.
* @see \Cake\Utility\Security::randomBytes()
*/
public static function insecureRandomBytes(int $length): string
{
$length *= 2;
$bytes = '';
$byteLength = 0;
while ($byteLength < $length) {
$bytes .= static::hash(Text::uuid() . uniqid((string)mt_rand(), true), 'sha512', true);
$byteLength = strlen($bytes);
}
$bytes = substr($bytes, 0, $length);
return pack('H*', $bytes);
}
/**
* Get the crypto implementation based on the loaded extensions.
*
* You can use this method to forcibly decide between openssl/custom implementations.
*
* @param \Cake\Utility\Crypto\OpenSsl|null $instance The crypto instance to use.
* @return \Cake\Utility\Crypto\OpenSsl Crypto instance.
* @throws \InvalidArgumentException When no compatible crypto extension is available.
* @psalm-suppress MoreSpecificReturnType
*/
public static function engine($instance = null)
{
if ($instance) {
return static::$_instance = $instance;
}
if (isset(static::$_instance)) {
/** @psalm-suppress LessSpecificReturnStatement */
return static::$_instance;
}
if (extension_loaded('openssl')) {
return static::$_instance = new OpenSsl();
}
throw new InvalidArgumentException(
'No compatible crypto engine available. ' .
'Load the openssl extension.'
);
}
/**
* Encrypt a value using AES-256.
*
* *Caveat* You cannot properly encrypt/decrypt data with trailing null bytes.
* Any trailing null bytes will be removed on decryption due to how PHP pads messages
* with nulls prior to encryption.
*
* @param string $plain The value to encrypt.
* @param string $key The 256 bit/32 byte key to use as a cipher key.
* @param string|null $hmacSalt The salt to use for the HMAC process.
* Leave null to use value of Security::getSalt().
* @return string Encrypted data.
* @throws \InvalidArgumentException On invalid data or key.
*/
public static function encrypt(string $plain, string $key, ?string $hmacSalt = null): string
{
self::_checkKey($key, 'encrypt()');
if ($hmacSalt === null) {
$hmacSalt = static::getSalt();
}
// Generate the encryption and hmac key.
$key = mb_substr(hash('sha256', $key . $hmacSalt), 0, 32, '8bit');
$crypto = static::engine();
$ciphertext = $crypto->encrypt($plain, $key);
$hmac = hash_hmac('sha256', $ciphertext, $key);
return $hmac . $ciphertext;
}
/**
* Check the encryption key for proper length.
*
* @param string $key Key to check.
* @param string $method The method the key is being checked for.
* @return void
* @throws \InvalidArgumentException When key length is not 256 bit/32 bytes
*/
protected static function _checkKey(string $key, string $method): void
{
if (mb_strlen($key, '8bit') < 32) {
throw new InvalidArgumentException(
sprintf('Invalid key for %s, key must be at least 256 bits (32 bytes) long.', $method)
);
}
}
/**
* Decrypt a value using AES-256.
*
* @param string $cipher The ciphertext to decrypt.
* @param string $key The 256 bit/32 byte key to use as a cipher key.
* @param string|null $hmacSalt The salt to use for the HMAC process.
* Leave null to use value of Security::getSalt().
* @return string|null Decrypted data. Any trailing null bytes will be removed.
* @throws \InvalidArgumentException On invalid data or key.
*/
public static function decrypt(string $cipher, string $key, ?string $hmacSalt = null): ?string
{
self::_checkKey($key, 'decrypt()');
if (empty($cipher)) {
throw new InvalidArgumentException('The data to decrypt cannot be empty.');
}
if ($hmacSalt === null) {
$hmacSalt = static::getSalt();
}
// Generate the encryption and hmac key.
$key = mb_substr(hash('sha256', $key . $hmacSalt), 0, 32, '8bit');
// Split out hmac for comparison
$macSize = 64;
$hmac = mb_substr($cipher, 0, $macSize, '8bit');
$cipher = mb_substr($cipher, $macSize, null, '8bit');
$compareHmac = hash_hmac('sha256', $cipher, $key);
if (!static::constantEquals($hmac, $compareHmac)) {
return null;
}
$crypto = static::engine();
return $crypto->decrypt($cipher, $key);
}
/**
* A timing attack resistant comparison that prefers native PHP implementations.
*
* @param mixed $original The original value.
* @param mixed $compare The comparison value.
* @return bool
* @since 3.6.2
*/
public static function constantEquals($original, $compare): bool
{
return is_string($original) && is_string($compare) && hash_equals($original, $compare);
}
/**
* Gets the HMAC salt to be used for encryption/decryption
* routines.
*
* @return string The currently configured salt
*/
public static function getSalt(): string
{
if (static::$_salt === null) {
throw new RuntimeException(
'Salt not set. Use Security::setSalt() to set one, ideally in `config/bootstrap.php`.'
);
}
return static::$_salt;
}
/**
* Sets the HMAC salt to be used for encryption/decryption
* routines.
*
* @param string $salt The salt to use for encryption routines.
* @return void
*/
public static function setSalt(string $salt): void
{
static::$_salt = $salt;
}
}
+1180
View File
File diff suppressed because it is too large Load Diff
+502
View File
@@ -0,0 +1,502 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 0.10.3
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Utility;
use Cake\Utility\Exception\XmlException;
use Closure;
use DOMDocument;
use DOMNode;
use DOMText;
use Exception;
use SimpleXMLElement;
/**
* XML handling for CakePHP.
*
* The methods in these classes enable the datasources that use XML to work.
*/
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>', ['return' => 'domdocument']);
* ```
*
* Building XML from a file path:
*
* ```
* $xml = Xml::build('/path/to/an/xml/file.xml', ['readFile' => true]);
* ```
*
* Building XML from a remote URL:
*
* ```
* use Cake\Http\Client;
*
* $http = new Client();
* $response = $http->get('http://example.com/example.xml');
* $xml = Xml::build($response->body());
* ```
*
* Building from an array:
*
* ```
* $value = [
* 'tags' => [
* 'tag' => [
* [
* 'id' => '1',
* 'name' => 'defect'
* ],
* [
* '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.
* - `readFile` Set to true to enable file reading. This is disabled by default to prevent
* local filesystem access. Only enable this setting when the input is safe.
* - `parseHuge` Enable the `LIBXML_PARSEHUGE` flag.
*
* If using array as input, you can pass `options` from Xml::fromArray.
*
* @param object|array|string $input XML string, a path to a file, a URL or an array
* @param array<string, mixed> $options The options to use
* @return \SimpleXMLElement|\DOMDocument SimpleXMLElement or DOMDocument
* @throws \Cake\Utility\Exception\XmlException
*/
public static function build($input, array $options = [])
{
$defaults = [
'return' => 'simplexml',
'loadEntities' => false,
'readFile' => false,
'parseHuge' => false,
];
$options += $defaults;
if (is_array($input) || is_object($input)) {
return static::fromArray($input, $options);
}
if ($options['readFile'] && file_exists($input)) {
return static::_loadXml(file_get_contents($input), $options);
}
if (!is_string($input)) {
$type = gettype($input);
throw new XmlException("Invalid input. {$type} cannot be parsed as XML.");
}
if (strpos($input, '<') !== false) {
return static::_loadXml($input, $options);
}
throw new XmlException('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<string, mixed> $options The options to use. See Xml::build()
* @return \SimpleXMLElement|\DOMDocument
* @throws \Cake\Utility\Exception\XmlException
*/
protected static function _loadXml(string $input, array $options)
{
return static::load(
$input,
$options,
function ($input, $options, $flags) {
if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
$flags |= LIBXML_NOCDATA;
$xml = new SimpleXMLElement($input, $flags);
} else {
$xml = new DOMDocument();
$xml->loadXML($input, $flags);
}
return $xml;
}
);
}
/**
* Parse the input html string and create either a SimpleXmlElement object or a DOMDocument.
*
* @param string $input The input html string to load.
* @param array<string, mixed> $options The options to use. See Xml::build()
* @return \SimpleXMLElement|\DOMDocument
* @throws \Cake\Utility\Exception\XmlException
*/
public static function loadHtml(string $input, array $options = [])
{
$defaults = [
'return' => 'simplexml',
'loadEntities' => false,
];
$options += $defaults;
return static::load(
$input,
$options,
function ($input, $options, $flags) {
$xml = new DOMDocument();
$xml->loadHTML($input, $flags);
if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
$xml = simplexml_import_dom($xml);
}
return $xml;
}
);
}
/**
* Parse the input data and create either a SimpleXmlElement object or a DOMDocument.
*
* @param string $input The input to load.
* @param array<string, mixed> $options The options to use. See Xml::build()
* @param \Closure $callable Closure that should return SimpleXMLElement or DOMDocument instance.
* @return \SimpleXMLElement|\DOMDocument
* @throws \Cake\Utility\Exception\XmlException
*/
protected static function load(string $input, array $options, Closure $callable)
{
$flags = 0;
if (!empty($options['parseHuge'])) {
$flags |= LIBXML_PARSEHUGE;
}
$internalErrors = libxml_use_internal_errors(true);
if (LIBXML_VERSION < 20900 && !$options['loadEntities']) {
$previousDisabledEntityLoader = libxml_disable_entity_loader(true);
} elseif ($options['loadEntities']) {
$flags |= LIBXML_NOENT;
}
try {
return $callable($input, $options, $flags);
} catch (Exception $e) {
throw new XmlException('Xml cannot be read. ' . $e->getMessage(), null, $e);
} finally {
if (isset($previousDisabledEntityLoader)) {
libxml_disable_entity_loader($previousDisabledEntityLoader);
}
libxml_use_internal_errors($internalErrors);
}
}
/**
* Transform an array into a SimpleXMLElement
*
* ### Options
*
* - `format` If create children ('tags') or attributes ('attributes').
* - `pretty` Returns formatted Xml when set to `true`. Defaults to `false`
* - `version` Version of XML document. Default is 1.0.
* - `encoding` Encoding of XML document. If null remove from XML header.
* Defaults to the application's encoding
* - `return` If return object of SimpleXMLElement ('simplexml')
* or DOMDocument ('domdocument'). Default is SimpleXMLElement.
*
* Using the following data:
*
* ```
* $value = [
* 'root' => [
* 'tag' => [
* '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, 'attributes');` Will generate:
*
* `<root><tag id="1" value="defect">description</tag></root>`
*
* @param object|array $input Array with data or a collection instance.
* @param array<string, mixed> $options The options to use.
* @return \SimpleXMLElement|\DOMDocument SimpleXMLElement or DOMDocument
* @throws \Cake\Utility\Exception\XmlException
*/
public static function fromArray($input, array $options = [])
{
if (is_object($input) && method_exists($input, 'toArray') && is_callable([$input, 'toArray'])) {
$input = $input->toArray();
}
if (!is_array($input) || count($input) !== 1) {
throw new XmlException('Invalid input.');
}
$key = key($input);
if (is_int($key)) {
throw new XmlException('The key of input must be alphanumeric');
}
$defaults = [
'format' => 'tags',
'version' => '1.0',
'encoding' => mb_internal_encoding(),
'return' => 'simplexml',
'pretty' => false,
];
$options += $defaults;
$dom = new DOMDocument($options['version'], $options['encoding']);
if ($options['pretty']) {
$dom->formatOutput = true;
}
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 children from array
*
* @param \DOMDocument $dom Handler to DOMDocument
* @param \DOMDocument|\DOMElement $node Handler to DOMElement (child)
* @param array $data Array of data to append to the $node.
* @param string $format Either 'attributes' or 'tags'. This determines where nested keys go.
* @return void
* @throws \Cake\Utility\Exception\XmlException
*/
protected static function _fromArray(DOMDocument $dom, $node, &$data, $format): void
{
if (empty($data) || !is_array($data)) {
return;
}
foreach ($data as $key => $value) {
if (is_string($key)) {
if (is_object($value) && method_exists($value, 'toArray') && is_callable([$value, 'toArray'])) {
$value = $value->toArray();
}
if (!is_array($value)) {
if (is_bool($value)) {
$value = (int)$value;
} elseif ($value === null) {
$value = '';
}
$isNamespace = strpos($key, 'xmlns:');
if ($isNamespace !== false) {
/** @psalm-suppress PossiblyUndefinedMethod */
$node->setAttributeNS('http://www.w3.org/2000/xmlns/', $key, (string)$value);
continue;
}
if ($key[0] !== '@' && $format === 'tags') {
if (!is_numeric($value)) {
// Escape special characters
// https://www.w3.org/TR/REC-xml/#syntax
// https://bugs.php.net/bug.php?id=36795
$child = $dom->createElement($key, '');
$child->appendChild(new DOMText((string)$value));
} else {
$child = $dom->createElement($key, (string)$value);
}
$node->appendChild($child);
} else {
if ($key[0] === '@') {
$key = substr($key, 1);
}
$attribute = $dom->createAttribute($key);
$attribute->appendChild($dom->createTextNode((string)$value));
$node->appendChild($attribute);
}
} else {
if ($key[0] === '@') {
throw new XmlException('Invalid array');
}
if (is_numeric(implode('', array_keys($value)))) {
// List
foreach ($value as $item) {
$itemData = compact('dom', 'node', 'key', 'format');
$itemData['value'] = $item;
static::_createChild($itemData);
}
} else {
// Struct
static::_createChild(compact('dom', 'node', 'key', 'value', 'format'));
}
}
} else {
throw new XmlException('Invalid array');
}
}
}
/**
* Helper to _fromArray(). It will create children of arrays
*
* @param array<string, mixed> $data Array with information to create children
* @return void
*/
protected static function _createChild(array $data): void
{
$data += [
'dom' => null,
'node' => null,
'key' => null,
'value' => null,
'format' => null,
];
$value = $data['value'];
$dom = $data['dom'];
$key = $data['key'];
$format = $data['format'];
$node = $data['node'];
$childNS = $childValue = null;
if (is_object($value) && method_exists($value, 'toArray') && is_callable([$value, 'toArray'])) {
$value = $value->toArray();
}
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 || $value === '0') {
$childValue = (string)$value;
}
$child = $dom->createElement($key);
if ($childValue !== null) {
$child->appendChild($dom->createTextNode($childValue));
}
if ($childNS) {
$child->setAttribute('xmlns', $childNS);
}
static::_fromArray($dom, $child, $value, $format);
$node->appendChild($child);
}
/**
* Returns this XML structure as an array.
*
* @param \SimpleXMLElement|\DOMDocument|\DOMNode $obj SimpleXMLElement, DOMDocument or DOMNode instance
* @return array Array representation of the XML structure.
* @throws \Cake\Utility\Exception\XmlException
*/
public static function toArray($obj): array
{
if ($obj instanceof DOMNode) {
$obj = simplexml_import_dom($obj);
}
if (!($obj instanceof SimpleXMLElement)) {
throw new XmlException('The input is not instance of SimpleXMLElement, DOMDocument or DOMNode.');
}
$result = [];
$namespaces = array_merge(['' => ''], $obj->getNamespaces(true));
static::_toArray($obj, $result, '', array_keys($namespaces));
return $result;
}
/**
* Recursive method to toArray
*
* @param \SimpleXMLElement $xml SimpleXMLElement object
* @param array<string, mixed> $parentData Parent array with data
* @param string $ns Namespace of current child
* @param array<string> $namespaces List of namespaces in XML
* @return void
*/
protected static function _toArray(SimpleXMLElement $xml, array &$parentData, string $ns, array $namespaces): void
{
$data = [];
foreach ($namespaces as $namespace) {
/**
* @psalm-suppress PossiblyNullIterator
* @var string $key
*/
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) {
static::_toArray($child, $data, $namespace, $namespaces);
}
}
$asString = trim((string)$xml);
if (empty($data)) {
$data = $asString;
} elseif ($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] = [$parentData[$name]];
}
$parentData[$name][] = $data;
} else {
$parentData[$name] = $data;
}
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
use Cake\Utility\Inflector;
// Store the initial state
Inflector::reset();
+43
View File
@@ -0,0 +1,43 @@
{
"name": "cakephp/utility",
"description": "CakePHP Utility classes such as Inflector, String, Hash, and Security",
"type": "library",
"keywords": [
"cakephp",
"utility",
"inflector",
"string",
"hash",
"security"
],
"homepage": "https://cakephp.org",
"license": "MIT",
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/utility/graphs/contributors"
}
],
"support": {
"issues": "https://github.com/cakephp/cakephp/issues",
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"source": "https://github.com/cakephp/utility"
},
"require": {
"php": ">=7.4.0",
"cakephp/core": "^4.0"
},
"suggest": {
"ext-intl": "To use Text::transliterate() or Text::slug()",
"lib-ICU": "To use Text::transliterate() or Text::slug()"
},
"autoload": {
"psr-4": {
"Cake\\Utility\\": "."
},
"files": [
"bootstrap.php"
]
}
}