String.php 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. <?php
  2. /**
  3. * String handling methods.
  4. *
  5. * PHP 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright 2005-2010, 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-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package cake
  16. * @subpackage cake.cake.libs
  17. * @since CakePHP(tm) v 1.2.0.5551
  18. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  19. */
  20. /**
  21. * String handling methods.
  22. *
  23. *
  24. * @package cake
  25. * @subpackage cake.cake.libs
  26. */
  27. class String {
  28. /**
  29. * Generate a random UUID
  30. *
  31. * @see http://www.ietf.org/rfc/rfc4122.txt
  32. * @return RFC 4122 UUID
  33. */
  34. public static function uuid() {
  35. $node = env('SERVER_ADDR');
  36. $pid = null;
  37. if (strpos($node, ':') !== false) {
  38. if (substr_count($node, '::')) {
  39. $node = str_replace(
  40. '::', str_repeat(':0000', 8 - substr_count($node, ':')) . ':', $node
  41. );
  42. }
  43. $node = explode(':', $node) ;
  44. $ipv6 = '' ;
  45. foreach ($node as $id) {
  46. $ipv6 .= str_pad(base_convert($id, 16, 2), 16, 0, STR_PAD_LEFT);
  47. }
  48. $node = base_convert($ipv6, 2, 10);
  49. if (strlen($node) < 38) {
  50. $node = null;
  51. } else {
  52. $node = crc32($node);
  53. }
  54. } elseif (empty($node)) {
  55. $host = env('HOSTNAME');
  56. if (empty($host)) {
  57. $host = env('HOST');
  58. }
  59. if (!empty($host)) {
  60. $ip = gethostbyname($host);
  61. if ($ip === $host) {
  62. $node = crc32($host);
  63. } else {
  64. $node = ip2long($ip);
  65. }
  66. }
  67. } elseif ($node !== '127.0.0.1') {
  68. $node = ip2long($node);
  69. } else {
  70. $node = null;
  71. }
  72. if (empty($node)) {
  73. $node = crc32(Configure::read('Security.salt'));
  74. }
  75. if (function_exists('zend_thread_id')) {
  76. $pid = zend_thread_id();
  77. } else {
  78. $pid = getmypid();
  79. }
  80. if (!$pid || $pid > 65535) {
  81. $pid = mt_rand(0, 0xfff) | 0x4000;
  82. }
  83. list($timeMid, $timeLow) = explode(' ', microtime());
  84. $uuid = sprintf(
  85. "%08x-%04x-%04x-%02x%02x-%04x%08x", (int)$timeLow, (int)substr($timeMid, 2) & 0xffff,
  86. mt_rand(0, 0xfff) | 0x4000, mt_rand(0, 0x3f) | 0x80, mt_rand(0, 0xff), $pid, $node
  87. );
  88. return $uuid;
  89. }
  90. /**
  91. * Tokenizes a string using $separator, ignoring any instance of $separator that appears between
  92. * $leftBound and $rightBound
  93. *
  94. * @param string $data The data to tokenize
  95. * @param string $separator The token to split the data on.
  96. * @param string $leftBound The left boundary to ignore separators in.
  97. * @param string $rightBound The right boundary to ignore separators in.
  98. * @return array Array of tokens in $data.
  99. */
  100. public static function tokenize($data, $separator = ',', $leftBound = '(', $rightBound = ')') {
  101. if (empty($data) || is_array($data)) {
  102. return $data;
  103. }
  104. $depth = 0;
  105. $offset = 0;
  106. $buffer = '';
  107. $results = array();
  108. $length = strlen($data);
  109. $open = false;
  110. while ($offset <= $length) {
  111. $tmpOffset = -1;
  112. $offsets = array(
  113. strpos($data, $separator, $offset),
  114. strpos($data, $leftBound, $offset),
  115. strpos($data, $rightBound, $offset)
  116. );
  117. for ($i = 0; $i < 3; $i++) {
  118. if ($offsets[$i] !== false && ($offsets[$i] < $tmpOffset || $tmpOffset == -1)) {
  119. $tmpOffset = $offsets[$i];
  120. }
  121. }
  122. if ($tmpOffset !== -1) {
  123. $buffer .= substr($data, $offset, ($tmpOffset - $offset));
  124. if ($data{$tmpOffset} == $separator && $depth == 0) {
  125. $results[] = $buffer;
  126. $buffer = '';
  127. } else {
  128. $buffer .= $data{$tmpOffset};
  129. }
  130. if ($leftBound != $rightBound) {
  131. if ($data{$tmpOffset} == $leftBound) {
  132. $depth++;
  133. }
  134. if ($data{$tmpOffset} == $rightBound) {
  135. $depth--;
  136. }
  137. } else {
  138. if ($data{$tmpOffset} == $leftBound) {
  139. if (!$open) {
  140. $depth++;
  141. $open = true;
  142. } else {
  143. $depth--;
  144. $open = false;
  145. }
  146. }
  147. }
  148. $offset = ++$tmpOffset;
  149. } else {
  150. $results[] = $buffer . substr($data, $offset);
  151. $offset = $length + 1;
  152. }
  153. }
  154. if (empty($results) && !empty($buffer)) {
  155. $results[] = $buffer;
  156. }
  157. if (!empty($results)) {
  158. $data = array_map('trim', $results);
  159. } else {
  160. $data = array();
  161. }
  162. return $data;
  163. }
  164. /**
  165. * Replaces variable placeholders inside a $str with any given $data. Each key in the $data array
  166. * corresponds to a variable placeholder name in $str.
  167. * Example: `String::insert(':name is :age years old.', array('name' => 'Bob', '65'));`
  168. * Returns: Bob is 65 years old.
  169. *
  170. * Available $options are:
  171. *
  172. * - before: The character or string in front of the name of the variable placeholder (Defaults to `:`)
  173. * - after: The character or string after the name of the variable placeholder (Defaults to null)
  174. * - escape: The character or string used to escape the before character / string (Defaults to `\`)
  175. * - format: A regex to use for matching variable placeholders. Default is: `/(?<!\\)\:%s/`
  176. * (Overwrites before, after, breaks escape / clean)
  177. * - clean: A boolean or array with instructions for String::cleanInsert
  178. *
  179. * @param string $str A string containing variable placeholders
  180. * @param string $data A key => val array where each key stands for a placeholder variable name
  181. * to be replaced with val
  182. * @param string $options An array of options, see description above
  183. * @return string
  184. */
  185. public static function insert($str, $data, $options = array()) {
  186. $defaults = array(
  187. 'before' => ':', 'after' => null, 'escape' => '\\', 'format' => null, 'clean' => false
  188. );
  189. $options += $defaults;
  190. $format = $options['format'];
  191. $data = (array)$data;
  192. if (empty($data)) {
  193. return ($options['clean']) ? String::cleanInsert($str, $options) : $str;
  194. }
  195. if (!isset($format)) {
  196. $format = sprintf(
  197. '/(?<!%s)%s%%s%s/',
  198. preg_quote($options['escape'], '/'),
  199. str_replace('%', '%%', preg_quote($options['before'], '/')),
  200. str_replace('%', '%%', preg_quote($options['after'], '/'))
  201. );
  202. }
  203. if (strpos($str, '?') !== false && is_numeric(key($data))) {
  204. $offset = 0;
  205. while (($pos = strpos($str, '?', $offset)) !== false) {
  206. $val = array_shift($data);
  207. $offset = $pos + strlen($val);
  208. $str = substr_replace($str, $val, $pos, 1);
  209. }
  210. return ($options['clean']) ? String::cleanInsert($str, $options) : $str;
  211. } else {
  212. asort($data);
  213. $hashKeys = array();
  214. foreach ($data as $key => $value) {
  215. $hashKeys[] = crc32($key);
  216. }
  217. $tempData = array_combine(array_keys($data), array_values($hashKeys));
  218. krsort($tempData);
  219. foreach ($tempData as $key => $hashVal) {
  220. $key = sprintf($format, preg_quote($key, '/'));
  221. $str = preg_replace($key, $hashVal, $str);
  222. }
  223. $dataReplacements = array_combine($hashKeys, array_values($data));
  224. foreach ($dataReplacements as $tmpHash => $tmpValue) {
  225. $tmpValue = (is_array($tmpValue)) ? '' : $tmpValue;
  226. $str = str_replace($tmpHash, $tmpValue, $str);
  227. }
  228. }
  229. if (!isset($options['format']) && isset($options['before'])) {
  230. $str = str_replace($options['escape'].$options['before'], $options['before'], $str);
  231. }
  232. return ($options['clean']) ? String::cleanInsert($str, $options) : $str;
  233. }
  234. /**
  235. * Cleans up a String::insert() formated string with given $options depending on the 'clean' key in
  236. * $options. The default method used is text but html is also available. The goal of this function
  237. * is to replace all whitespace and uneeded markup around placeholders that did not get replaced
  238. * by String::insert().
  239. *
  240. * @param string $str
  241. * @param string $options
  242. * @return string
  243. * @see String::insert()
  244. */
  245. public static function cleanInsert($str, $options) {
  246. $clean = $options['clean'];
  247. if (!$clean) {
  248. return $str;
  249. }
  250. if ($clean === true) {
  251. $clean = array('method' => 'text');
  252. }
  253. if (!is_array($clean)) {
  254. $clean = array('method' => $options['clean']);
  255. }
  256. switch ($clean['method']) {
  257. case 'html':
  258. $clean = array_merge(array(
  259. 'word' => '[\w,.]+',
  260. 'andText' => true,
  261. 'replacement' => '',
  262. ), $clean);
  263. $kleenex = sprintf(
  264. '/[\s]*[a-z]+=(")(%s%s%s[\s]*)+\\1/i',
  265. preg_quote($options['before'], '/'),
  266. $clean['word'],
  267. preg_quote($options['after'], '/')
  268. );
  269. $str = preg_replace($kleenex, $clean['replacement'], $str);
  270. if ($clean['andText']) {
  271. $options['clean'] = array('method' => 'text');
  272. $str = String::cleanInsert($str, $options);
  273. }
  274. break;
  275. case 'text':
  276. $clean = array_merge(array(
  277. 'word' => '[\w,.]+',
  278. 'gap' => '[\s]*(?:(?:and|or)[\s]*)?',
  279. 'replacement' => '',
  280. ), $clean);
  281. $kleenex = sprintf(
  282. '/(%s%s%s%s|%s%s%s%s)/',
  283. preg_quote($options['before'], '/'),
  284. $clean['word'],
  285. preg_quote($options['after'], '/'),
  286. $clean['gap'],
  287. $clean['gap'],
  288. preg_quote($options['before'], '/'),
  289. $clean['word'],
  290. preg_quote($options['after'], '/')
  291. );
  292. $str = preg_replace($kleenex, $clean['replacement'], $str);
  293. break;
  294. }
  295. return $str;
  296. }
  297. /**
  298. * Wraps text to a specific width, can optionally wrap at word breaks.
  299. *
  300. * ### Options
  301. *
  302. * - `width` The width to wrap to. Defaults to 72
  303. * - `wordWrap` Only wrap on words breaks (spaces) Defaults to true.
  304. * - `indent` String to indent with. Defaults to null.
  305. * - `indentAt` 0 based index to start indenting at. Defaults to 0.
  306. *
  307. * @param string $text Text the text to format.
  308. * @param mixed $options Array of options to use, or an integer to wrap the text to.
  309. * @return string Formatted text.
  310. */
  311. public static function wrap($text, $options = array()) {
  312. if (is_numeric($options)) {
  313. $options = array('width' => $options);
  314. }
  315. $options += array('width' => 72, 'wordWrap' => true, 'indent' => null, 'indentAt' => 0);
  316. if ($options['wordWrap']) {
  317. $wrapped = wordwrap($text, $options['width'], "\n");
  318. } else {
  319. $wrapped = trim(chunk_split($text, $options['width'] - 1, "\n"));
  320. }
  321. if (!empty($options['indent'])) {
  322. $chunks = explode("\n", $wrapped);
  323. for ($i = $options['indentAt'], $len = count($chunks); $i < $len; $i++) {
  324. $chunks[$i] = $options['indent'] . $chunks[$i];
  325. }
  326. $wrapped = implode("\n", $chunks);
  327. }
  328. return $wrapped;
  329. }
  330. }