String.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 1.2.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Utility;
  16. use InvalidArgumentException;
  17. /**
  18. * String handling methods.
  19. *
  20. */
  21. class String {
  22. /**
  23. * Generate a random UUID version 4
  24. *
  25. * @see http://www.ietf.org/rfc/rfc4122.txt
  26. * @return string RFC 4122 UUID
  27. * @copyright Matt Farina MIT License https://github.com/lootils/uuid/blob/master/LICENSE
  28. */
  29. public static function uuid() {
  30. return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
  31. // 32 bits for "time_low"
  32. mt_rand(0, 65535), mt_rand(0, 65535),
  33. // 16 bits for "time_mid"
  34. mt_rand(0, 65535),
  35. // 12 bits before the 0100 of (version) 4 for "time_hi_and_version"
  36. mt_rand(0, 4095) | 0x4000,
  37. // 16 bits, 8 bits for "clk_seq_hi_res",
  38. // 8 bits for "clk_seq_low",
  39. // two most significant bits holds zero and one for variant DCE1.1
  40. mt_rand(0, 0x3fff) | 0x8000,
  41. // 48 bits for "node"
  42. mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535)
  43. );
  44. }
  45. /**
  46. * Tokenizes a string using $separator, ignoring any instance of $separator that appears between
  47. * $leftBound and $rightBound.
  48. *
  49. * @param string $data The data to tokenize.
  50. * @param string $separator The token to split the data on.
  51. * @param string $leftBound The left boundary to ignore separators in.
  52. * @param string $rightBound The right boundary to ignore separators in.
  53. * @return mixed Array of tokens in $data or original input if empty.
  54. */
  55. public static function tokenize($data, $separator = ',', $leftBound = '(', $rightBound = ')') {
  56. if (empty($data)) {
  57. return array();
  58. }
  59. $depth = 0;
  60. $offset = 0;
  61. $buffer = '';
  62. $results = array();
  63. $length = strlen($data);
  64. $open = false;
  65. while ($offset <= $length) {
  66. $tmpOffset = -1;
  67. $offsets = array(
  68. strpos($data, $separator, $offset),
  69. strpos($data, $leftBound, $offset),
  70. strpos($data, $rightBound, $offset)
  71. );
  72. for ($i = 0; $i < 3; $i++) {
  73. if ($offsets[$i] !== false && ($offsets[$i] < $tmpOffset || $tmpOffset == -1)) {
  74. $tmpOffset = $offsets[$i];
  75. }
  76. }
  77. if ($tmpOffset !== -1) {
  78. $buffer .= substr($data, $offset, ($tmpOffset - $offset));
  79. if (!$depth && $data{$tmpOffset} === $separator) {
  80. $results[] = $buffer;
  81. $buffer = '';
  82. } else {
  83. $buffer .= $data{$tmpOffset};
  84. }
  85. if ($leftBound !== $rightBound) {
  86. if ($data{$tmpOffset} === $leftBound) {
  87. $depth++;
  88. }
  89. if ($data{$tmpOffset} === $rightBound) {
  90. $depth--;
  91. }
  92. } else {
  93. if ($data{$tmpOffset} === $leftBound) {
  94. if (!$open) {
  95. $depth++;
  96. $open = true;
  97. } else {
  98. $depth--;
  99. }
  100. }
  101. }
  102. $offset = ++$tmpOffset;
  103. } else {
  104. $results[] = $buffer . substr($data, $offset);
  105. $offset = $length + 1;
  106. }
  107. }
  108. if (empty($results) && !empty($buffer)) {
  109. $results[] = $buffer;
  110. }
  111. if (!empty($results)) {
  112. return array_map('trim', $results);
  113. }
  114. return array();
  115. }
  116. /**
  117. * Replaces variable placeholders inside a $str with any given $data. Each key in the $data array
  118. * corresponds to a variable placeholder name in $str.
  119. * Example: `String::insert(':name is :age years old.', array('name' => 'Bob', '65'));`
  120. * Returns: Bob is 65 years old.
  121. *
  122. * Available $options are:
  123. *
  124. * - before: The character or string in front of the name of the variable placeholder (Defaults to `:`)
  125. * - after: The character or string after the name of the variable placeholder (Defaults to null)
  126. * - escape: The character or string used to escape the before character / string (Defaults to `\`)
  127. * - format: A regex to use for matching variable placeholders. Default is: `/(?<!\\)\:%s/`
  128. * (Overwrites before, after, breaks escape / clean)
  129. * - clean: A boolean or array with instructions for String::cleanInsert
  130. *
  131. * @param string $str A string containing variable placeholders
  132. * @param array $data A key => val array where each key stands for a placeholder variable name
  133. * to be replaced with val
  134. * @param array $options An array of options, see description above
  135. * @return string
  136. */
  137. public static function insert($str, $data, array $options = array()) {
  138. $defaults = array(
  139. 'before' => ':', 'after' => null, 'escape' => '\\', 'format' => null, 'clean' => false
  140. );
  141. $options += $defaults;
  142. $format = $options['format'];
  143. $data = (array)$data;
  144. if (empty($data)) {
  145. return ($options['clean']) ? String::cleanInsert($str, $options) : $str;
  146. }
  147. if (!isset($format)) {
  148. $format = sprintf(
  149. '/(?<!%s)%s%%s%s/',
  150. preg_quote($options['escape'], '/'),
  151. str_replace('%', '%%', preg_quote($options['before'], '/')),
  152. str_replace('%', '%%', preg_quote($options['after'], '/'))
  153. );
  154. }
  155. if (strpos($str, '?') !== false && is_numeric(key($data))) {
  156. $offset = 0;
  157. while (($pos = strpos($str, '?', $offset)) !== false) {
  158. $val = array_shift($data);
  159. $offset = $pos + strlen($val);
  160. $str = substr_replace($str, $val, $pos, 1);
  161. }
  162. return ($options['clean']) ? String::cleanInsert($str, $options) : $str;
  163. }
  164. asort($data);
  165. $dataKeys = array_keys($data);
  166. $hashKeys = array_map('crc32', $dataKeys);
  167. $tempData = array_combine($dataKeys, $hashKeys);
  168. krsort($tempData);
  169. foreach ($tempData as $key => $hashVal) {
  170. $key = sprintf($format, preg_quote($key, '/'));
  171. $str = preg_replace($key, $hashVal, $str);
  172. }
  173. $dataReplacements = array_combine($hashKeys, array_values($data));
  174. foreach ($dataReplacements as $tmpHash => $tmpValue) {
  175. $tmpValue = (is_array($tmpValue)) ? '' : $tmpValue;
  176. $str = str_replace($tmpHash, $tmpValue, $str);
  177. }
  178. if (!isset($options['format']) && isset($options['before'])) {
  179. $str = str_replace($options['escape'] . $options['before'], $options['before'], $str);
  180. }
  181. return ($options['clean']) ? String::cleanInsert($str, $options) : $str;
  182. }
  183. /**
  184. * Cleans up a String::insert() formatted string with given $options depending on the 'clean' key in
  185. * $options. The default method used is text but html is also available. The goal of this function
  186. * is to replace all whitespace and unneeded markup around placeholders that did not get replaced
  187. * by String::insert().
  188. *
  189. * @param string $str String to clean.
  190. * @param array $options Options list.
  191. * @return string
  192. * @see String::insert()
  193. */
  194. public static function cleanInsert($str, array $options) {
  195. $clean = $options['clean'];
  196. if (!$clean) {
  197. return $str;
  198. }
  199. if ($clean === true) {
  200. $clean = array('method' => 'text');
  201. }
  202. if (!is_array($clean)) {
  203. $clean = array('method' => $options['clean']);
  204. }
  205. switch ($clean['method']) {
  206. case 'html':
  207. $clean += array(
  208. 'word' => '[\w,.]+',
  209. 'andText' => true,
  210. 'replacement' => '',
  211. );
  212. $kleenex = sprintf(
  213. '/[\s]*[a-z]+=(")(%s%s%s[\s]*)+\\1/i',
  214. preg_quote($options['before'], '/'),
  215. $clean['word'],
  216. preg_quote($options['after'], '/')
  217. );
  218. $str = preg_replace($kleenex, $clean['replacement'], $str);
  219. if ($clean['andText']) {
  220. $options['clean'] = array('method' => 'text');
  221. $str = String::cleanInsert($str, $options);
  222. }
  223. break;
  224. case 'text':
  225. $clean += array(
  226. 'word' => '[\w,.]+',
  227. 'gap' => '[\s]*(?:(?:and|or)[\s]*)?',
  228. 'replacement' => '',
  229. );
  230. $kleenex = sprintf(
  231. '/(%s%s%s%s|%s%s%s%s)/',
  232. preg_quote($options['before'], '/'),
  233. $clean['word'],
  234. preg_quote($options['after'], '/'),
  235. $clean['gap'],
  236. $clean['gap'],
  237. preg_quote($options['before'], '/'),
  238. $clean['word'],
  239. preg_quote($options['after'], '/')
  240. );
  241. $str = preg_replace($kleenex, $clean['replacement'], $str);
  242. break;
  243. }
  244. return $str;
  245. }
  246. /**
  247. * Wraps text to a specific width, can optionally wrap at word breaks.
  248. *
  249. * ### Options
  250. *
  251. * - `width` The width to wrap to. Defaults to 72.
  252. * - `wordWrap` Only wrap on words breaks (spaces) Defaults to true.
  253. * - `indent` String to indent with. Defaults to null.
  254. * - `indentAt` 0 based index to start indenting at. Defaults to 0.
  255. *
  256. * @param string $text The text to format.
  257. * @param array|int $options Array of options to use, or an integer to wrap the text to.
  258. * @return string Formatted text.
  259. */
  260. public static function wrap($text, $options = []) {
  261. if (is_numeric($options)) {
  262. $options = array('width' => $options);
  263. }
  264. $options += array('width' => 72, 'wordWrap' => true, 'indent' => null, 'indentAt' => 0);
  265. if ($options['wordWrap']) {
  266. $wrapped = self::wordWrap($text, $options['width'], "\n");
  267. } else {
  268. $wrapped = trim(chunk_split($text, $options['width'] - 1, "\n"));
  269. }
  270. if (!empty($options['indent'])) {
  271. $chunks = explode("\n", $wrapped);
  272. for ($i = $options['indentAt'], $len = count($chunks); $i < $len; $i++) {
  273. $chunks[$i] = $options['indent'] . $chunks[$i];
  274. }
  275. $wrapped = implode("\n", $chunks);
  276. }
  277. return $wrapped;
  278. }
  279. /**
  280. * Unicode aware version of wordwrap.
  281. *
  282. * @param string $text The text to format.
  283. * @param int $width The width to wrap to. Defaults to 72.
  284. * @param string $break The line is broken using the optional break parameter. Defaults to '\n'.
  285. * @param bool $cut If the cut is set to true, the string is always wrapped at the specified width.
  286. * @return string Formatted text.
  287. */
  288. public static function wordWrap($text, $width = 72, $break = "\n", $cut = false) {
  289. if ($cut) {
  290. $parts = array();
  291. while (mb_strlen($text) > 0) {
  292. $part = mb_substr($text, 0, $width);
  293. $parts[] = trim($part);
  294. $text = trim(mb_substr($text, mb_strlen($part)));
  295. }
  296. return implode($break, $parts);
  297. }
  298. $parts = array();
  299. while (mb_strlen($text) > 0) {
  300. if ($width >= mb_strlen($text)) {
  301. $parts[] = trim($text);
  302. break;
  303. }
  304. $part = mb_substr($text, 0, $width);
  305. $nextChar = mb_substr($text, $width, 1);
  306. if ($nextChar !== ' ') {
  307. $breakAt = mb_strrpos($part, ' ');
  308. if ($breakAt === false) {
  309. $breakAt = mb_strpos($text, ' ', $width);
  310. }
  311. if ($breakAt === false) {
  312. $parts[] = trim($text);
  313. break;
  314. }
  315. $part = mb_substr($text, 0, $breakAt);
  316. }
  317. $part = trim($part);
  318. $parts[] = $part;
  319. $text = trim(mb_substr($text, mb_strlen($part)));
  320. }
  321. return implode($break, $parts);
  322. }
  323. /**
  324. * Highlights a given phrase in a text. You can specify any expression in highlighter that
  325. * may include the \1 expression to include the $phrase found.
  326. *
  327. * ### Options:
  328. *
  329. * - `format` The piece of html with that the phrase will be highlighted
  330. * - `html` If true, will ignore any HTML tags, ensuring that only the correct text is highlighted
  331. * - `regex` a custom regex rule that is used to match words, default is '|$tag|iu'
  332. *
  333. * @param string $text Text to search the phrase in.
  334. * @param string|array $phrase The phrase or phrases that will be searched.
  335. * @param array $options An array of html attributes and options.
  336. * @return string The highlighted text
  337. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::highlight
  338. */
  339. public static function highlight($text, $phrase, array $options = array()) {
  340. if (empty($phrase)) {
  341. return $text;
  342. }
  343. $defaults = array(
  344. 'format' => '<span class="highlight">\1</span>',
  345. 'html' => false,
  346. 'regex' => "|%s|iu"
  347. );
  348. $options += $defaults;
  349. extract($options);
  350. if (is_array($phrase)) {
  351. $replace = array();
  352. $with = array();
  353. foreach ($phrase as $key => $segment) {
  354. $segment = '(' . preg_quote($segment, '|') . ')';
  355. if ($html) {
  356. $segment = "(?![^<]+>)$segment(?![^<]+>)";
  357. }
  358. $with[] = (is_array($format)) ? $format[$key] : $format;
  359. $replace[] = sprintf($options['regex'], $segment);
  360. }
  361. return preg_replace($replace, $with, $text);
  362. }
  363. $phrase = '(' . preg_quote($phrase, '|') . ')';
  364. if ($html) {
  365. $phrase = "(?![^<]+>)$phrase(?![^<]+>)";
  366. }
  367. return preg_replace(sprintf($options['regex'], $phrase), $format, $text);
  368. }
  369. /**
  370. * Strips given text of all links (<a href=....).
  371. *
  372. * @param string $text Text
  373. * @return string The text without links
  374. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::stripLinks
  375. */
  376. public static function stripLinks($text) {
  377. return preg_replace('|<a\s+[^>]+>|im', '', preg_replace('|<\/a>|im', '', $text));
  378. }
  379. /**
  380. * Truncates text starting from the end.
  381. *
  382. * Cuts a string to the length of $length and replaces the first characters
  383. * with the ellipsis if the text is longer than length.
  384. *
  385. * ### Options:
  386. *
  387. * - `ellipsis` Will be used as Beginning and prepended to the trimmed string
  388. * - `exact` If false, $text will not be cut mid-word
  389. *
  390. * @param string $text String to truncate.
  391. * @param int $length Length of returned string, including ellipsis.
  392. * @param array $options An array of options.
  393. * @return string Trimmed string.
  394. */
  395. public static function tail($text, $length = 100, array $options = array()) {
  396. $default = array(
  397. 'ellipsis' => '...', 'exact' => true
  398. );
  399. $options += $default;
  400. extract($options);
  401. if (mb_strlen($text) <= $length) {
  402. return $text;
  403. }
  404. $truncate = mb_substr($text, mb_strlen($text) - $length + mb_strlen($ellipsis));
  405. if (!$exact) {
  406. $spacepos = mb_strpos($truncate, ' ');
  407. $truncate = $spacepos === false ? '' : trim(mb_substr($truncate, $spacepos));
  408. }
  409. return $ellipsis . $truncate;
  410. }
  411. /**
  412. * Truncates text.
  413. *
  414. * Cuts a string to the length of $length and replaces the last characters
  415. * with the ellipsis if the text is longer than length.
  416. *
  417. * ### Options:
  418. *
  419. * - `ellipsis` Will be used as ending and appended to the trimmed string
  420. * - `exact` If false, $text will not be cut mid-word
  421. * - `html` If true, HTML tags would be handled correctly
  422. *
  423. * @param string $text String to truncate.
  424. * @param int $length Length of returned string, including ellipsis.
  425. * @param array $options An array of html attributes and options.
  426. * @return string Trimmed string.
  427. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::truncate
  428. */
  429. public static function truncate($text, $length = 100, array $options = array()) {
  430. $default = array(
  431. 'ellipsis' => '...', 'exact' => true, 'html' => false
  432. );
  433. if (!empty($options['html']) && strtolower(mb_internal_encoding()) === 'utf-8') {
  434. $default['ellipsis'] = "\xe2\x80\xa6";
  435. }
  436. $options += $default;
  437. extract($options);
  438. if ($html) {
  439. if (mb_strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
  440. return $text;
  441. }
  442. $totalLength = mb_strlen(strip_tags($ellipsis));
  443. $openTags = array();
  444. $truncate = '';
  445. preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER);
  446. foreach ($tags as $tag) {
  447. if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])) {
  448. if (preg_match('/<[\w]+[^>]*>/s', $tag[0])) {
  449. array_unshift($openTags, $tag[2]);
  450. } elseif (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag)) {
  451. $pos = array_search($closeTag[1], $openTags);
  452. if ($pos !== false) {
  453. array_splice($openTags, $pos, 1);
  454. }
  455. }
  456. }
  457. $truncate .= $tag[1];
  458. $contentLength = mb_strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $tag[3]));
  459. if ($contentLength + $totalLength > $length) {
  460. $left = $length - $totalLength;
  461. $entitiesLength = 0;
  462. 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)) {
  463. foreach ($entities[0] as $entity) {
  464. if ($entity[1] + 1 - $entitiesLength <= $left) {
  465. $left--;
  466. $entitiesLength += mb_strlen($entity[0]);
  467. } else {
  468. break;
  469. }
  470. }
  471. }
  472. $truncate .= mb_substr($tag[3], 0, $left + $entitiesLength);
  473. break;
  474. } else {
  475. $truncate .= $tag[3];
  476. $totalLength += $contentLength;
  477. }
  478. if ($totalLength >= $length) {
  479. break;
  480. }
  481. }
  482. } else {
  483. if (mb_strlen($text) <= $length) {
  484. return $text;
  485. }
  486. $truncate = mb_substr($text, 0, $length - mb_strlen($ellipsis));
  487. }
  488. if (!$exact) {
  489. $spacepos = mb_strrpos($truncate, ' ');
  490. if ($html) {
  491. $truncateCheck = mb_substr($truncate, 0, $spacepos);
  492. $lastOpenTag = mb_strrpos($truncateCheck, '<');
  493. $lastCloseTag = mb_strrpos($truncateCheck, '>');
  494. if ($lastOpenTag > $lastCloseTag) {
  495. preg_match_all('/<[\w]+[^>]*>/s', $truncate, $lastTagMatches);
  496. $lastTag = array_pop($lastTagMatches[0]);
  497. $spacepos = mb_strrpos($truncate, $lastTag) + mb_strlen($lastTag);
  498. }
  499. $bits = mb_substr($truncate, $spacepos);
  500. preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER);
  501. if (!empty($droppedTags)) {
  502. if (!empty($openTags)) {
  503. foreach ($droppedTags as $closingTag) {
  504. if (!in_array($closingTag[1], $openTags)) {
  505. array_unshift($openTags, $closingTag[1]);
  506. }
  507. }
  508. } else {
  509. foreach ($droppedTags as $closingTag) {
  510. $openTags[] = $closingTag[1];
  511. }
  512. }
  513. }
  514. }
  515. $truncate = mb_substr($truncate, 0, $spacepos);
  516. }
  517. $truncate .= $ellipsis;
  518. if ($html) {
  519. foreach ($openTags as $tag) {
  520. $truncate .= '</' . $tag . '>';
  521. }
  522. }
  523. return $truncate;
  524. }
  525. /**
  526. * Extracts an excerpt from the text surrounding the phrase with a number of characters on each side
  527. * determined by radius.
  528. *
  529. * @param string $text String to search the phrase in
  530. * @param string $phrase Phrase that will be searched for
  531. * @param int $radius The amount of characters that will be returned on each side of the founded phrase
  532. * @param string $ellipsis Ending that will be appended
  533. * @return string Modified string
  534. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::excerpt
  535. */
  536. public static function excerpt($text, $phrase, $radius = 100, $ellipsis = '...') {
  537. if (empty($text) || empty($phrase)) {
  538. return static::truncate($text, $radius * 2, array('ellipsis' => $ellipsis));
  539. }
  540. $append = $prepend = $ellipsis;
  541. $phraseLen = mb_strlen($phrase);
  542. $textLen = mb_strlen($text);
  543. $pos = mb_strpos(mb_strtolower($text), mb_strtolower($phrase));
  544. if ($pos === false) {
  545. return mb_substr($text, 0, $radius) . $ellipsis;
  546. }
  547. $startPos = $pos - $radius;
  548. if ($startPos <= 0) {
  549. $startPos = 0;
  550. $prepend = '';
  551. }
  552. $endPos = $pos + $phraseLen + $radius;
  553. if ($endPos >= $textLen) {
  554. $endPos = $textLen;
  555. $append = '';
  556. }
  557. $excerpt = mb_substr($text, $startPos, $endPos - $startPos);
  558. $excerpt = $prepend . $excerpt . $append;
  559. return $excerpt;
  560. }
  561. /**
  562. * Creates a comma separated list where the last two items are joined with 'and', forming natural language.
  563. *
  564. * @param array $list The list to be joined.
  565. * @param string $and The word used to join the last and second last items together with. Defaults to 'and'.
  566. * @param string $separator The separator used to join all the other items together. Defaults to ', '.
  567. * @return string The glued together string.
  568. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::toList
  569. */
  570. public static function toList(array $list, $and = null, $separator = ', ') {
  571. if ($and === null) {
  572. $and = __d('cake', 'and');
  573. }
  574. if (count($list) > 1) {
  575. return implode($separator, array_slice($list, null, -1)) . ' ' . $and . ' ' . array_pop($list);
  576. }
  577. return array_pop($list);
  578. }
  579. /**
  580. * Check if the string contain multibyte characters
  581. *
  582. * @param string $string value to test
  583. * @return bool
  584. */
  585. public static function isMultibyte($string) {
  586. $length = strlen($string);
  587. for ($i = 0; $i < $length; $i++) {
  588. $value = ord(($string[$i]));
  589. if ($value > 128) {
  590. return true;
  591. }
  592. }
  593. return false;
  594. }
  595. /**
  596. * Converts a multibyte character string
  597. * to the decimal value of the character
  598. *
  599. * @param string $string String to convert.
  600. * @return array
  601. */
  602. public static function utf8($string) {
  603. $map = array();
  604. $values = array();
  605. $find = 1;
  606. $length = strlen($string);
  607. for ($i = 0; $i < $length; $i++) {
  608. $value = ord($string[$i]);
  609. if ($value < 128) {
  610. $map[] = $value;
  611. } else {
  612. if (empty($values)) {
  613. $find = ($value < 224) ? 2 : 3;
  614. }
  615. $values[] = $value;
  616. if (count($values) === $find) {
  617. if ($find == 3) {
  618. $map[] = (($values[0] % 16) * 4096) + (($values[1] % 64) * 64) + ($values[2] % 64);
  619. } else {
  620. $map[] = (($values[0] % 32) * 64) + ($values[1] % 64);
  621. }
  622. $values = array();
  623. $find = 1;
  624. }
  625. }
  626. }
  627. return $map;
  628. }
  629. /**
  630. * Converts the decimal value of a multibyte character string
  631. * to a string
  632. *
  633. * @param array $array Array
  634. * @return string
  635. */
  636. public static function ascii(array $array) {
  637. $ascii = '';
  638. foreach ($array as $utf8) {
  639. if ($utf8 < 128) {
  640. $ascii .= chr($utf8);
  641. } elseif ($utf8 < 2048) {
  642. $ascii .= chr(192 + (($utf8 - ($utf8 % 64)) / 64));
  643. $ascii .= chr(128 + ($utf8 % 64));
  644. } else {
  645. $ascii .= chr(224 + (($utf8 - ($utf8 % 4096)) / 4096));
  646. $ascii .= chr(128 + ((($utf8 % 4096) - ($utf8 % 64)) / 64));
  647. $ascii .= chr(128 + ($utf8 % 64));
  648. }
  649. }
  650. return $ascii;
  651. }
  652. /**
  653. * Converts filesize from human readable string to bytes
  654. *
  655. * @param string $size Size in human readable string like '5MB', '5M', '500B', '50kb' etc.
  656. * @param mixed $default Value to be returned when invalid size was used, for example 'Unknown type'
  657. * @return mixed Number of bytes as integer on success, `$default` on failure if not false
  658. * @throws \InvalidArgumentException On invalid Unit type.
  659. * @link http://book.cakephp.org/3.0/en/core-libraries/helpers/text.html
  660. */
  661. public static function parseFileSize($size, $default = false) {
  662. if (ctype_digit($size)) {
  663. return (int)$size;
  664. }
  665. $size = strtoupper($size);
  666. $l = -2;
  667. $i = array_search(substr($size, -2), array('KB', 'MB', 'GB', 'TB', 'PB'));
  668. if ($i === false) {
  669. $l = -1;
  670. $i = array_search(substr($size, -1), array('K', 'M', 'G', 'T', 'P'));
  671. }
  672. if ($i !== false) {
  673. $size = substr($size, 0, $l);
  674. return $size * pow(1024, $i + 1);
  675. }
  676. if (substr($size, -1) === 'B' && ctype_digit(substr($size, 0, -1))) {
  677. $size = substr($size, 0, -1);
  678. return (int)$size;
  679. }
  680. if ($default !== false) {
  681. return $default;
  682. }
  683. throw new InvalidArgumentException('No unit type.');
  684. }
  685. }