PageRenderTime 52ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/Cake/Utility/Set.php

https://bitbucket.org/udeshika/fake_twitter
PHP | 1117 lines | 766 code | 66 blank | 285 comment | 252 complexity | f6f03b08dd3bc441a0aea5aee5051b4e MD5 | raw file
  1. <?php
  2. /**
  3. * Library of array functions for Cake.
  4. *
  5. * PHP 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice.
  12. *
  13. * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package Cake.Utility
  16. * @since CakePHP(tm) v 1.2.0
  17. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  18. */
  19. App::uses('String', 'Utility');
  20. /**
  21. * Class used for manipulation of arrays.
  22. *
  23. * @package Cake.Utility
  24. */
  25. class Set {
  26. /**
  27. * This function can be thought of as a hybrid between PHP's array_merge and array_merge_recursive. The difference
  28. * to the two is that if an array key contains another array then the function behaves recursive (unlike array_merge)
  29. * but does not do if for keys containing strings (unlike array_merge_recursive).
  30. * See the unit test for more information.
  31. *
  32. * Note: This function will work with an unlimited amount of arguments and typecasts non-array parameters into arrays.
  33. *
  34. * @param array $arr1 Array to be merged
  35. * @param array $arr2 Array to merge with
  36. * @return array Merged array
  37. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::merge
  38. */
  39. public static function merge($arr1, $arr2 = null) {
  40. $args = func_get_args();
  41. $r = (array)current($args);
  42. while (($arg = next($args)) !== false) {
  43. foreach ((array)$arg as $key => $val) {
  44. if (!empty($r[$key]) && is_array($r[$key]) && is_array($val)) {
  45. $r[$key] = Set::merge($r[$key], $val);
  46. } elseif (is_int($key)) {
  47. $r[] = $val;
  48. } else {
  49. $r[$key] = $val;
  50. }
  51. }
  52. }
  53. return $r;
  54. }
  55. /**
  56. * Filters empty elements out of a route array, excluding '0'.
  57. *
  58. * @param array $var Either an array to filter, or value when in callback
  59. * @return mixed Either filtered array, or true/false when in callback
  60. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::filter
  61. */
  62. public static function filter(array $var) {
  63. foreach ($var as $k => $v) {
  64. if (is_array($v)) {
  65. $var[$k] = Set::filter($v);
  66. }
  67. }
  68. return array_filter($var, array('Set', '_filter'));
  69. }
  70. /**
  71. * Set::filter callback function
  72. *
  73. * @param array $var Array to filter.
  74. * @return boolean
  75. */
  76. protected static function _filter($var) {
  77. if ($var === 0 || $var === '0' || !empty($var)) {
  78. return true;
  79. }
  80. return false;
  81. }
  82. /**
  83. * Pushes the differences in $array2 onto the end of $array
  84. *
  85. * @param mixed $array Original array
  86. * @param mixed $array2 Differences to push
  87. * @return array Combined array
  88. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::pushDiff
  89. */
  90. public static function pushDiff($array, $array2) {
  91. if (empty($array) && !empty($array2)) {
  92. return $array2;
  93. }
  94. if (!empty($array) && !empty($array2)) {
  95. foreach ($array2 as $key => $value) {
  96. if (!array_key_exists($key, $array)) {
  97. $array[$key] = $value;
  98. } else {
  99. if (is_array($value)) {
  100. $array[$key] = Set::pushDiff($array[$key], $array2[$key]);
  101. }
  102. }
  103. }
  104. }
  105. return $array;
  106. }
  107. /**
  108. * Maps the contents of the Set object to an object hierarchy.
  109. * Maintains numeric keys as arrays of objects
  110. *
  111. * @param string $class A class name of the type of object to map to
  112. * @param string $tmp A temporary class name used as $class if $class is an array
  113. * @return object Hierarchical object
  114. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::map
  115. */
  116. public static function map($class = 'stdClass', $tmp = 'stdClass') {
  117. if (is_array($class)) {
  118. $val = $class;
  119. $class = $tmp;
  120. }
  121. if (empty($val)) {
  122. return null;
  123. }
  124. return Set::_map($val, $class);
  125. }
  126. /**
  127. * Maps the given value as an object. If $value is an object,
  128. * it returns $value. Otherwise it maps $value as an object of
  129. * type $class, and if primary assign _name_ $key on first array.
  130. * If $value is not empty, it will be used to set properties of
  131. * returned object (recursively). If $key is numeric will maintain array
  132. * structure
  133. *
  134. * @param array $array Array to map
  135. * @param string $class Class name
  136. * @param boolean $primary whether to assign first array key as the _name_
  137. * @return mixed Mapped object
  138. */
  139. protected static function _map(&$array, $class, $primary = false) {
  140. if ($class === true) {
  141. $out = new stdClass;
  142. } else {
  143. $out = new $class;
  144. }
  145. if (is_array($array)) {
  146. $keys = array_keys($array);
  147. foreach ($array as $key => $value) {
  148. if ($keys[0] === $key && $class !== true) {
  149. $primary = true;
  150. }
  151. if (is_numeric($key)) {
  152. if (is_object($out)) {
  153. $out = get_object_vars($out);
  154. }
  155. $out[$key] = Set::_map($value, $class);
  156. if (is_object($out[$key])) {
  157. if ($primary !== true && is_array($value) && Set::countDim($value, true) === 2) {
  158. if (!isset($out[$key]->_name_)) {
  159. $out[$key]->_name_ = $primary;
  160. }
  161. }
  162. }
  163. } elseif (is_array($value)) {
  164. if ($primary === true) {
  165. if (!isset($out->_name_)) {
  166. $out->_name_ = $key;
  167. }
  168. $primary = false;
  169. foreach ($value as $key2 => $value2) {
  170. $out->{$key2} = Set::_map($value2, true);
  171. }
  172. } else {
  173. if (!is_numeric($key)) {
  174. $out->{$key} = Set::_map($value, true, $key);
  175. if (is_object($out->{$key}) && !is_numeric($key)) {
  176. if (!isset($out->{$key}->_name_)) {
  177. $out->{$key}->_name_ = $key;
  178. }
  179. }
  180. } else {
  181. $out->{$key} = Set::_map($value, true);
  182. }
  183. }
  184. } else {
  185. $out->{$key} = $value;
  186. }
  187. }
  188. } else {
  189. $out = $array;
  190. }
  191. return $out;
  192. }
  193. /**
  194. * Checks to see if all the values in the array are numeric
  195. *
  196. * @param array $array The array to check. If null, the value of the current Set object
  197. * @return boolean true if values are numeric, false otherwise
  198. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::numeric
  199. */
  200. public static function numeric($array = null) {
  201. if (empty($array)) {
  202. return null;
  203. }
  204. if ($array === range(0, count($array) - 1)) {
  205. return true;
  206. }
  207. $numeric = true;
  208. $keys = array_keys($array);
  209. $count = count($keys);
  210. for ($i = 0; $i < $count; $i++) {
  211. if (!is_numeric($array[$keys[$i]])) {
  212. $numeric = false;
  213. break;
  214. }
  215. }
  216. return $numeric;
  217. }
  218. /**
  219. * Return a value from an array list if the key exists.
  220. *
  221. * If a comma separated $list is passed arrays are numeric with the key of the first being 0
  222. * $list = 'no, yes' would translate to $list = array(0 => 'no', 1 => 'yes');
  223. *
  224. * If an array is used, keys can be strings example: array('no' => 0, 'yes' => 1);
  225. *
  226. * $list defaults to 0 = no 1 = yes if param is not passed
  227. *
  228. * @param mixed $select Key in $list to return
  229. * @param mixed $list can be an array or a comma-separated list.
  230. * @return string the value of the array key or null if no match
  231. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::enum
  232. */
  233. public static function enum($select, $list = null) {
  234. if (empty($list)) {
  235. $list = array('no', 'yes');
  236. }
  237. $return = null;
  238. $list = Set::normalize($list, false);
  239. if (array_key_exists($select, $list)) {
  240. $return = $list[$select];
  241. }
  242. return $return;
  243. }
  244. /**
  245. * Returns a series of values extracted from an array, formatted in a format string.
  246. *
  247. * @param array $data Source array from which to extract the data
  248. * @param string $format Format string into which values will be inserted, see sprintf()
  249. * @param array $keys An array containing one or more Set::extract()-style key paths
  250. * @return array An array of strings extracted from $keys and formatted with $format
  251. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::format
  252. */
  253. public static function format($data, $format, $keys) {
  254. $extracted = array();
  255. $count = count($keys);
  256. if (!$count) {
  257. return;
  258. }
  259. for ($i = 0; $i < $count; $i++) {
  260. $extracted[] = Set::extract($data, $keys[$i]);
  261. }
  262. $out = array();
  263. $data = $extracted;
  264. $count = count($data[0]);
  265. if (preg_match_all('/\{([0-9]+)\}/msi', $format, $keys2) && isset($keys2[1])) {
  266. $keys = $keys2[1];
  267. $format = preg_split('/\{([0-9]+)\}/msi', $format);
  268. $count2 = count($format);
  269. for ($j = 0; $j < $count; $j++) {
  270. $formatted = '';
  271. for ($i = 0; $i <= $count2; $i++) {
  272. if (isset($format[$i])) {
  273. $formatted .= $format[$i];
  274. }
  275. if (isset($keys[$i]) && isset($data[$keys[$i]][$j])) {
  276. $formatted .= $data[$keys[$i]][$j];
  277. }
  278. }
  279. $out[] = $formatted;
  280. }
  281. } else {
  282. $count2 = count($data);
  283. for ($j = 0; $j < $count; $j++) {
  284. $args = array();
  285. for ($i = 0; $i < $count2; $i++) {
  286. if (array_key_exists($j, $data[$i])) {
  287. $args[] = $data[$i][$j];
  288. }
  289. }
  290. $out[] = vsprintf($format, $args);
  291. }
  292. }
  293. return $out;
  294. }
  295. /**
  296. * Implements partial support for XPath 2.0. If $path is an array or $data is empty it the call
  297. * is delegated to Set::classicExtract.
  298. *
  299. * #### Currently implemented selectors:
  300. *
  301. * - /User/id (similar to the classic {n}.User.id)
  302. * - /User[2]/name (selects the name of the second User)
  303. * - /User[id>2] (selects all Users with an id > 2)
  304. * - /User[id>2][<5] (selects all Users with an id > 2 but < 5)
  305. * - /Post/Comment[author_name=john]/../name (Selects the name of all Posts that have at least one Comment written by john)
  306. * - /Posts[name] (Selects all Posts that have a 'name' key)
  307. * - /Comment/.[1] (Selects the contents of the first comment)
  308. * - /Comment/.[:last] (Selects the last comment)
  309. * - /Comment/.[:first] (Selects the first comment)
  310. * - /Comment[text=/cakephp/i] (Selects the all comments that have a text matching the regex /cakephp/i)
  311. * - /Comment/@* (Selects the all key names of all comments)
  312. *
  313. * #### Other limitations:
  314. *
  315. * - Only absolute paths starting with a single '/' are supported right now
  316. *
  317. * **Warning**: Even so it has plenty of unit tests the XPath support has not gone through a lot of
  318. * real-world testing. Please report Bugs as you find them. Suggestions for additional features to
  319. * implement are also very welcome!
  320. *
  321. * @param string $path An absolute XPath 2.0 path
  322. * @param array $data An array of data to extract from
  323. * @param array $options Currently only supports 'flatten' which can be disabled for higher XPath-ness
  324. * @return array An array of matched items
  325. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::extract
  326. */
  327. public static function extract($path, $data = null, $options = array()) {
  328. if (is_string($data)) {
  329. $tmp = $data;
  330. $data = $path;
  331. $path = $tmp;
  332. }
  333. if (strpos($path, '/') === false) {
  334. return Set::classicExtract($data, $path);
  335. }
  336. if (empty($data)) {
  337. return array();
  338. }
  339. if ($path === '/') {
  340. return $data;
  341. }
  342. $contexts = $data;
  343. $options = array_merge(array('flatten' => true), $options);
  344. if (!isset($contexts[0])) {
  345. $current = current($data);
  346. if ((is_array($current) && count($data) < 1) || !is_array($current) || !Set::numeric(array_keys($data))) {
  347. $contexts = array($data);
  348. }
  349. }
  350. $tokens = array_slice(preg_split('/(?<!=|\\\\)\/(?![a-z-\s]*\])/', $path), 1);
  351. do {
  352. $token = array_shift($tokens);
  353. $conditions = false;
  354. if (preg_match_all('/\[([^=]+=\/[^\/]+\/|[^\]]+)\]/', $token, $m)) {
  355. $conditions = $m[1];
  356. $token = substr($token, 0, strpos($token, '['));
  357. }
  358. $matches = array();
  359. foreach ($contexts as $key => $context) {
  360. if (!isset($context['trace'])) {
  361. $context = array('trace' => array(null), 'item' => $context, 'key' => $key);
  362. }
  363. if ($token === '..') {
  364. if (count($context['trace']) == 1) {
  365. $context['trace'][] = $context['key'];
  366. }
  367. $parent = implode('/', $context['trace']) . '/.';
  368. $context['item'] = Set::extract($parent, $data);
  369. $context['key'] = array_pop($context['trace']);
  370. if (isset($context['trace'][1]) && $context['trace'][1] > 0) {
  371. $context['item'] = $context['item'][0];
  372. } elseif (!empty($context['item'][$key])) {
  373. $context['item'] = $context['item'][$key];
  374. } else {
  375. $context['item'] = array_shift($context['item']);
  376. }
  377. $matches[] = $context;
  378. continue;
  379. }
  380. if ($token === '@*' && is_array($context['item'])) {
  381. $matches[] = array(
  382. 'trace' => array_merge($context['trace'], (array)$key),
  383. 'key' => $key,
  384. 'item' => array_keys($context['item']),
  385. );
  386. } elseif (is_array($context['item'])
  387. && array_key_exists($token, $context['item'])
  388. && !(strval($key) === strval($token) && count($tokens) == 1 && $tokens[0] === '.')) {
  389. $items = $context['item'][$token];
  390. if (!is_array($items)) {
  391. $items = array($items);
  392. } elseif (!isset($items[0])) {
  393. $current = current($items);
  394. $currentKey = key($items);
  395. if (!is_array($current) || (is_array($current) && count($items) <= 1 && !is_numeric($currentKey))) {
  396. $items = array($items);
  397. }
  398. }
  399. foreach ($items as $key => $item) {
  400. $ctext = array($context['key']);
  401. if (!is_numeric($key)) {
  402. $ctext[] = $token;
  403. $tok = array_shift($tokens);
  404. if (isset($items[$tok])) {
  405. $ctext[] = $tok;
  406. $item = $items[$tok];
  407. $matches[] = array(
  408. 'trace' => array_merge($context['trace'], $ctext),
  409. 'key' => $tok,
  410. 'item' => $item,
  411. );
  412. break;
  413. } elseif ($tok !== null) {
  414. array_unshift($tokens, $tok);
  415. }
  416. } else {
  417. $key = $token;
  418. }
  419. $matches[] = array(
  420. 'trace' => array_merge($context['trace'], $ctext),
  421. 'key' => $key,
  422. 'item' => $item,
  423. );
  424. }
  425. } elseif ($key === $token || (ctype_digit($token) && $key == $token) || $token === '.') {
  426. $context['trace'][] = $key;
  427. $matches[] = array(
  428. 'trace' => $context['trace'],
  429. 'key' => $key,
  430. 'item' => $context['item'],
  431. );
  432. }
  433. }
  434. if ($conditions) {
  435. foreach ($conditions as $condition) {
  436. $filtered = array();
  437. $length = count($matches);
  438. foreach ($matches as $i => $match) {
  439. if (Set::matches(array($condition), $match['item'], $i + 1, $length)) {
  440. $filtered[$i] = $match;
  441. }
  442. }
  443. $matches = $filtered;
  444. }
  445. }
  446. $contexts = $matches;
  447. if (empty($tokens)) {
  448. break;
  449. }
  450. } while(1);
  451. $r = array();
  452. foreach ($matches as $match) {
  453. if ((!$options['flatten'] || is_array($match['item'])) && !is_int($match['key'])) {
  454. $r[] = array($match['key'] => $match['item']);
  455. } else {
  456. $r[] = $match['item'];
  457. }
  458. }
  459. return $r;
  460. }
  461. /**
  462. * This function can be used to see if a single item or a given xpath match certain conditions.
  463. *
  464. * @param mixed $conditions An array of condition strings or an XPath expression
  465. * @param array $data An array of data to execute the match on
  466. * @param integer $i Optional: The 'nth'-number of the item being matched.
  467. * @param integer $length
  468. * @return boolean
  469. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::matches
  470. */
  471. public static function matches($conditions, $data = array(), $i = null, $length = null) {
  472. if (empty($conditions)) {
  473. return true;
  474. }
  475. if (is_string($conditions)) {
  476. return !!Set::extract($conditions, $data);
  477. }
  478. foreach ($conditions as $condition) {
  479. if ($condition === ':last') {
  480. if ($i != $length) {
  481. return false;
  482. }
  483. continue;
  484. } elseif ($condition === ':first') {
  485. if ($i != 1) {
  486. return false;
  487. }
  488. continue;
  489. }
  490. if (!preg_match('/(.+?)([><!]?[=]|[><])(.*)/', $condition, $match)) {
  491. if (ctype_digit($condition)) {
  492. if ($i != $condition) {
  493. return false;
  494. }
  495. } elseif (preg_match_all('/(?:^[0-9]+|(?<=,)[0-9]+)/', $condition, $matches)) {
  496. return in_array($i, $matches[0]);
  497. } elseif (!array_key_exists($condition, $data)) {
  498. return false;
  499. }
  500. continue;
  501. }
  502. list(, $key, $op, $expected) = $match;
  503. if (!isset($data[$key])) {
  504. return false;
  505. }
  506. $val = $data[$key];
  507. if ($op === '=' && $expected && $expected{0} === '/') {
  508. return preg_match($expected, $val);
  509. }
  510. if ($op === '=' && $val != $expected) {
  511. return false;
  512. }
  513. if ($op === '!=' && $val == $expected) {
  514. return false;
  515. }
  516. if ($op === '>' && $val <= $expected) {
  517. return false;
  518. }
  519. if ($op === '<' && $val >= $expected) {
  520. return false;
  521. }
  522. if ($op === '<=' && $val > $expected) {
  523. return false;
  524. }
  525. if ($op === '>=' && $val < $expected) {
  526. return false;
  527. }
  528. }
  529. return true;
  530. }
  531. /**
  532. * Gets a value from an array or object that is contained in a given path using an array path syntax, i.e.:
  533. * "{n}.Person.{[a-z]+}" - Where "{n}" represents a numeric key, "Person" represents a string literal,
  534. * and "{[a-z]+}" (i.e. any string literal enclosed in brackets besides {n} and {s}) is interpreted as
  535. * a regular expression.
  536. *
  537. * @param array $data Array from where to extract
  538. * @param mixed $path As an array, or as a dot-separated string.
  539. * @return array Extracted data
  540. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::classicExtract
  541. */
  542. public static function classicExtract($data, $path = null) {
  543. if (empty($path)) {
  544. return $data;
  545. }
  546. if (is_object($data)) {
  547. if (!($data instanceof ArrayAccess || $data instanceof Traversable)) {
  548. $data = get_object_vars($data);
  549. }
  550. }
  551. if (empty($data)) {
  552. return null;
  553. }
  554. if (is_string($path) && strpos($path, '{') !== false) {
  555. $path = String::tokenize($path, '.', '{', '}');
  556. } elseif (is_string($path)) {
  557. $path = explode('.', $path);
  558. }
  559. $tmp = array();
  560. if (empty($path)) {
  561. return null;
  562. }
  563. foreach ($path as $i => $key) {
  564. if (is_numeric($key) && intval($key) > 0 || $key === '0') {
  565. if (isset($data[intval($key)])) {
  566. $data = $data[intval($key)];
  567. } else {
  568. return null;
  569. }
  570. } elseif ($key === '{n}') {
  571. foreach ($data as $j => $val) {
  572. if (is_int($j)) {
  573. $tmpPath = array_slice($path, $i + 1);
  574. if (empty($tmpPath)) {
  575. $tmp[] = $val;
  576. } else {
  577. $tmp[] = Set::classicExtract($val, $tmpPath);
  578. }
  579. }
  580. }
  581. return $tmp;
  582. } elseif ($key === '{s}') {
  583. foreach ($data as $j => $val) {
  584. if (is_string($j)) {
  585. $tmpPath = array_slice($path, $i + 1);
  586. if (empty($tmpPath)) {
  587. $tmp[] = $val;
  588. } else {
  589. $tmp[] = Set::classicExtract($val, $tmpPath);
  590. }
  591. }
  592. }
  593. return $tmp;
  594. } elseif (false !== strpos($key, '{') && false !== strpos($key, '}')) {
  595. $pattern = substr($key, 1, -1);
  596. foreach ($data as $j => $val) {
  597. if (preg_match('/^' . $pattern . '/s', $j) !== 0) {
  598. $tmpPath = array_slice($path, $i + 1);
  599. if (empty($tmpPath)) {
  600. $tmp[$j] = $val;
  601. } else {
  602. $tmp[$j] = Set::classicExtract($val, $tmpPath);
  603. }
  604. }
  605. }
  606. return $tmp;
  607. } else {
  608. if (isset($data[$key])) {
  609. $data = $data[$key];
  610. } else {
  611. return null;
  612. }
  613. }
  614. }
  615. return $data;
  616. }
  617. /**
  618. * Inserts $data into an array as defined by $path.
  619. *
  620. * @param mixed $list Where to insert into
  621. * @param mixed $path A dot-separated string.
  622. * @param array $data Data to insert
  623. * @return array
  624. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::insert
  625. */
  626. public static function insert($list, $path, $data = null) {
  627. if (!is_array($path)) {
  628. $path = explode('.', $path);
  629. }
  630. $_list =& $list;
  631. $count = count($path);
  632. foreach ($path as $i => $key) {
  633. if (is_numeric($key) && intval($key) > 0 || $key === '0') {
  634. $key = intval($key);
  635. }
  636. if ($i === $count - 1 && is_array($_list)) {
  637. $_list[$key] = $data;
  638. } else {
  639. if (!isset($_list[$key])) {
  640. $_list[$key] = array();
  641. }
  642. $_list =& $_list[$key];
  643. }
  644. if (!is_array($_list)) {
  645. return array();
  646. }
  647. }
  648. return $list;
  649. }
  650. /**
  651. * Removes an element from a Set or array as defined by $path.
  652. *
  653. * @param mixed $list From where to remove
  654. * @param mixed $path A dot-separated string.
  655. * @return array Array with $path removed from its value
  656. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::remove
  657. */
  658. public static function remove($list, $path = null) {
  659. if (empty($path)) {
  660. return $list;
  661. }
  662. if (!is_array($path)) {
  663. $path = explode('.', $path);
  664. }
  665. $_list =& $list;
  666. foreach ($path as $i => $key) {
  667. if (is_numeric($key) && intval($key) > 0 || $key === '0') {
  668. $key = intval($key);
  669. }
  670. if ($i === count($path) - 1) {
  671. unset($_list[$key]);
  672. } else {
  673. if (!isset($_list[$key])) {
  674. return $list;
  675. }
  676. $_list =& $_list[$key];
  677. }
  678. }
  679. return $list;
  680. }
  681. /**
  682. * Checks if a particular path is set in an array
  683. *
  684. * @param mixed $data Data to check on
  685. * @param mixed $path A dot-separated string.
  686. * @return boolean true if path is found, false otherwise
  687. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::check
  688. */
  689. public static function check($data, $path = null) {
  690. if (empty($path)) {
  691. return $data;
  692. }
  693. if (!is_array($path)) {
  694. $path = explode('.', $path);
  695. }
  696. foreach ($path as $i => $key) {
  697. if (is_numeric($key) && intval($key) > 0 || $key === '0') {
  698. $key = intval($key);
  699. }
  700. if ($i === count($path) - 1) {
  701. return (is_array($data) && array_key_exists($key, $data));
  702. }
  703. if (!is_array($data) || !array_key_exists($key, $data)) {
  704. return false;
  705. }
  706. $data =& $data[$key];
  707. }
  708. return true;
  709. }
  710. /**
  711. * Computes the difference between a Set and an array, two Sets, or two arrays
  712. *
  713. * @param mixed $val1 First value
  714. * @param mixed $val2 Second value
  715. * @return array Returns the key => value pairs that are not common in $val1 and $val2
  716. * The expression for this function is ($val1 - $val2) + ($val2 - ($val1 - $val2))
  717. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::diff
  718. */
  719. public static function diff($val1, $val2 = null) {
  720. if (empty($val1)) {
  721. return (array)$val2;
  722. }
  723. if (empty($val2)) {
  724. return (array)$val1;
  725. }
  726. $intersection = array_intersect_key($val1, $val2);
  727. while (($key = key($intersection)) !== null) {
  728. if ($val1[$key] == $val2[$key]) {
  729. unset($val1[$key]);
  730. unset($val2[$key]);
  731. }
  732. next($intersection);
  733. }
  734. return $val1 + $val2;
  735. }
  736. /**
  737. * Determines if one Set or array contains the exact keys and values of another.
  738. *
  739. * @param array $val1 First value
  740. * @param array $val2 Second value
  741. * @return boolean true if $val1 contains $val2, false otherwise
  742. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::contains
  743. */
  744. public static function contains($val1, $val2 = null) {
  745. if (empty($val1) || empty($val2)) {
  746. return false;
  747. }
  748. foreach ($val2 as $key => $val) {
  749. if (is_numeric($key)) {
  750. Set::contains($val, $val1);
  751. } else {
  752. if (!isset($val1[$key]) || $val1[$key] != $val) {
  753. return false;
  754. }
  755. }
  756. }
  757. return true;
  758. }
  759. /**
  760. * Counts the dimensions of an array. If $all is set to false (which is the default) it will
  761. * only consider the dimension of the first element in the array.
  762. *
  763. * @param array $array Array to count dimensions on
  764. * @param boolean $all Set to true to count the dimension considering all elements in array
  765. * @param integer $count Start the dimension count at this number
  766. * @return integer The number of dimensions in $array
  767. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::countDim
  768. */
  769. public static function countDim($array = null, $all = false, $count = 0) {
  770. if ($all) {
  771. $depth = array($count);
  772. if (is_array($array) && reset($array) !== false) {
  773. foreach ($array as $value) {
  774. $depth[] = Set::countDim($value, true, $count + 1);
  775. }
  776. }
  777. $return = max($depth);
  778. } else {
  779. if (is_array(reset($array))) {
  780. $return = Set::countDim(reset($array)) + 1;
  781. } else {
  782. $return = 1;
  783. }
  784. }
  785. return $return;
  786. }
  787. /**
  788. * Normalizes a string or array list.
  789. *
  790. * @param mixed $list List to normalize
  791. * @param boolean $assoc If true, $list will be converted to an associative array
  792. * @param string $sep If $list is a string, it will be split into an array with $sep
  793. * @param boolean $trim If true, separated strings will be trimmed
  794. * @return array
  795. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::normalize
  796. */
  797. public static function normalize($list, $assoc = true, $sep = ',', $trim = true) {
  798. if (is_string($list)) {
  799. $list = explode($sep, $list);
  800. if ($trim) {
  801. foreach ($list as $key => $value) {
  802. $list[$key] = trim($value);
  803. }
  804. }
  805. if ($assoc) {
  806. return Set::normalize($list);
  807. }
  808. } elseif (is_array($list)) {
  809. $keys = array_keys($list);
  810. $count = count($keys);
  811. $numeric = true;
  812. if (!$assoc) {
  813. for ($i = 0; $i < $count; $i++) {
  814. if (!is_int($keys[$i])) {
  815. $numeric = false;
  816. break;
  817. }
  818. }
  819. }
  820. if (!$numeric || $assoc) {
  821. $newList = array();
  822. for ($i = 0; $i < $count; $i++) {
  823. if (is_int($keys[$i])) {
  824. $newList[$list[$keys[$i]]] = null;
  825. } else {
  826. $newList[$keys[$i]] = $list[$keys[$i]];
  827. }
  828. }
  829. $list = $newList;
  830. }
  831. }
  832. return $list;
  833. }
  834. /**
  835. * Creates an associative array using a $path1 as the path to build its keys, and optionally
  836. * $path2 as path to get the values. If $path2 is not specified, all values will be initialized
  837. * to null (useful for Set::merge). You can optionally group the values by what is obtained when
  838. * following the path specified in $groupPath.
  839. *
  840. * @param mixed $data Array or object from where to extract keys and values
  841. * @param mixed $path1 As an array, or as a dot-separated string.
  842. * @param mixed $path2 As an array, or as a dot-separated string.
  843. * @param string $groupPath As an array, or as a dot-separated string.
  844. * @return array Combined array
  845. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::combine
  846. */
  847. public static function combine($data, $path1 = null, $path2 = null, $groupPath = null) {
  848. if (empty($data)) {
  849. return array();
  850. }
  851. if (is_object($data)) {
  852. if (!($data instanceof ArrayAccess || $data instanceof Traversable)) {
  853. $data = get_object_vars($data);
  854. }
  855. }
  856. if (is_array($path1)) {
  857. $format = array_shift($path1);
  858. $keys = Set::format($data, $format, $path1);
  859. } else {
  860. $keys = Set::extract($data, $path1);
  861. }
  862. if (empty($keys)) {
  863. return array();
  864. }
  865. if (!empty($path2) && is_array($path2)) {
  866. $format = array_shift($path2);
  867. $vals = Set::format($data, $format, $path2);
  868. } elseif (!empty($path2)) {
  869. $vals = Set::extract($data, $path2);
  870. } else {
  871. $count = count($keys);
  872. for ($i = 0; $i < $count; $i++) {
  873. $vals[$i] = null;
  874. }
  875. }
  876. if ($groupPath != null) {
  877. $group = Set::extract($data, $groupPath);
  878. if (!empty($group)) {
  879. $c = count($keys);
  880. for ($i = 0; $i < $c; $i++) {
  881. if (!isset($group[$i])) {
  882. $group[$i] = 0;
  883. }
  884. if (!isset($out[$group[$i]])) {
  885. $out[$group[$i]] = array();
  886. }
  887. $out[$group[$i]][$keys[$i]] = $vals[$i];
  888. }
  889. return $out;
  890. }
  891. }
  892. if (empty($vals)) {
  893. return array();
  894. }
  895. return array_combine($keys, $vals);
  896. }
  897. /**
  898. * Converts an object into an array.
  899. * @param object $object Object to reverse
  900. * @return array Array representation of given object
  901. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::reverse
  902. */
  903. public static function reverse($object) {
  904. $out = array();
  905. if ($object instanceof SimpleXMLElement) {
  906. return Xml::toArray($object);
  907. } else if (is_object($object)) {
  908. $keys = get_object_vars($object);
  909. if (isset($keys['_name_'])) {
  910. $identity = $keys['_name_'];
  911. unset($keys['_name_']);
  912. }
  913. $new = array();
  914. foreach ($keys as $key => $value) {
  915. if (is_array($value)) {
  916. $new[$key] = (array)Set::reverse($value);
  917. } else {
  918. if (isset($value->_name_)) {
  919. $new = array_merge($new, Set::reverse($value));
  920. } else {
  921. $new[$key] = Set::reverse($value);
  922. }
  923. }
  924. }
  925. if (isset($identity)) {
  926. $out[$identity] = $new;
  927. } else {
  928. $out = $new;
  929. }
  930. } elseif (is_array($object)) {
  931. foreach ($object as $key => $value) {
  932. $out[$key] = Set::reverse($value);
  933. }
  934. } else {
  935. $out = $object;
  936. }
  937. return $out;
  938. }
  939. /**
  940. * Collapses a multi-dimensional array into a single dimension, using a delimited array path for
  941. * each array element's key, i.e. array(array('Foo' => array('Bar' => 'Far'))) becomes
  942. * array('0.Foo.Bar' => 'Far').
  943. *
  944. * @param array $data Array to flatten
  945. * @param string $separator String used to separate array key elements in a path, defaults to '.'
  946. * @return array
  947. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::flatten
  948. */
  949. public static function flatten($data, $separator = '.') {
  950. $result = array();
  951. $path = null;
  952. if (is_array($separator)) {
  953. extract($separator, EXTR_OVERWRITE);
  954. }
  955. if (!is_null($path)) {
  956. $path .= $separator;
  957. }
  958. foreach ($data as $key => $val) {
  959. if (is_array($val)) {
  960. $result += (array)Set::flatten($val, array(
  961. 'separator' => $separator,
  962. 'path' => $path . $key
  963. ));
  964. } else {
  965. $result[$path . $key] = $val;
  966. }
  967. }
  968. return $result;
  969. }
  970. /**
  971. * Flattens an array for sorting
  972. *
  973. * @param array $results
  974. * @param string $key
  975. * @return array
  976. */
  977. protected static function _flatten($results, $key = null) {
  978. $stack = array();
  979. foreach ($results as $k => $r) {
  980. $id = $k;
  981. if (!is_null($key)) {
  982. $id = $key;
  983. }
  984. if (is_array($r) && !empty($r)) {
  985. $stack = array_merge($stack, Set::_flatten($r, $id));
  986. } else {
  987. $stack[] = array('id' => $id, 'value' => $r);
  988. }
  989. }
  990. return $stack;
  991. }
  992. /**
  993. * Sorts an array by any value, determined by a Set-compatible path
  994. *
  995. * @param array $data An array of data to sort
  996. * @param string $path A Set-compatible path to the array value
  997. * @param string $dir Direction of sorting - either ascending (ASC), or descending (DESC)
  998. * @return array Sorted array of data
  999. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::sort
  1000. */
  1001. public static function sort($data, $path, $dir) {
  1002. $originalKeys = array_keys($data);
  1003. if (is_numeric(implode('', $originalKeys))) {
  1004. $data = array_values($data);
  1005. }
  1006. $result = Set::_flatten(Set::extract($data, $path));
  1007. list($keys, $values) = array(Set::extract($result, '{n}.id'), Set::extract($result, '{n}.value'));
  1008. $dir = strtolower($dir);
  1009. if ($dir === 'asc') {
  1010. $dir = SORT_ASC;
  1011. } elseif ($dir === 'desc') {
  1012. $dir = SORT_DESC;
  1013. }
  1014. array_multisort($values, $dir, $keys, $dir);
  1015. $sorted = array();
  1016. $keys = array_unique($keys);
  1017. foreach ($keys as $k) {
  1018. $sorted[] = $data[$k];
  1019. }
  1020. return $sorted;
  1021. }
  1022. /**
  1023. * Allows the application of a callback method to elements of an
  1024. * array extracted by a Set::extract() compatible path.
  1025. *
  1026. * @param mixed $path Set-compatible path to the array value
  1027. * @param array $data An array of data to extract from & then process with the $callback.
  1028. * @param mixed $callback Callback method to be applied to extracted data.
  1029. * See http://ca2.php.net/manual/en/language.pseudo-types.php#language.types.callback for examples
  1030. * of callback formats.
  1031. * @param array $options Options are:
  1032. * - type : can be pass, map, or reduce. Map will handoff the given callback
  1033. * to array_map, reduce will handoff to array_reduce, and pass will
  1034. * use call_user_func_array().
  1035. * @return mixed Result of the callback when applied to extracted data
  1036. * @link http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::apply
  1037. */
  1038. public static function apply($path, $data, $callback, $options = array()) {
  1039. $defaults = array('type' => 'pass');
  1040. $options = array_merge($defaults, $options);
  1041. $extracted = Set::extract($path, $data);
  1042. if ($options['type'] === 'map') {
  1043. return array_map($callback, $extracted);
  1044. } elseif ($options['type'] === 'reduce') {
  1045. return array_reduce($extracted, $callback);
  1046. } elseif ($options['type'] === 'pass') {
  1047. return call_user_func_array($callback, array($extracted));
  1048. }
  1049. return null;
  1050. }
  1051. }