PageRenderTime 27ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/cake/libs/multibyte.php

https://github.com/msadouni/cakephp2x
PHP | 1153 lines | 665 code | 109 blank | 379 comment | 200 complexity | 8726e4eca3ee9ee099658bc5c6ab61de MD5 | raw file
  1. <?php
  2. /**
  3. * Multibyte handling methods.
  4. *
  5. *
  6. * PHP Version 5.x
  7. *
  8. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  9. * Copyright 2005-2009, Cake Software Foundation, Inc. (http://cakefoundation.org)
  10. *
  11. * Licensed under The MIT License
  12. * Redistributions of files must retain the above copyright notice.
  13. *
  14. * @copyright Copyright 2005-2009, Cake Software Foundation, Inc. (http://cakefoundation.org)
  15. * @link http://cakephp.org CakePHP(tm) Project
  16. * @package cake
  17. * @subpackage cake.cake.libs
  18. * @since CakePHP(tm) v 1.2.0.6833
  19. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  20. */
  21. if (function_exists('mb_internal_encoding')) {
  22. $encoding = Configure::read('App.encoding');
  23. if (!empty($encoding)) {
  24. mb_internal_encoding($encoding);
  25. }
  26. }
  27. /**
  28. * Find position of first occurrence of a case-insensitive string.
  29. *
  30. * @param string $haystack The string from which to get the position of the first occurrence of $needle.
  31. * @param string $needle The string to find in $haystack.
  32. * @param integer $offset The position in $haystack to start searching.
  33. * @param string $encoding Character encoding name to use. If it is omitted, internal character encoding is used.
  34. * @return integer|boolean The numeric position of the first occurrence of $needle in the $haystack string, or false
  35. * if $needle is not found.
  36. */
  37. if (!function_exists('mb_stripos')) {
  38. function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) {
  39. return Multibyte::stripos($haystack, $needle, $offset);
  40. }
  41. }
  42. /**
  43. * Finds first occurrence of a string within another, case insensitive.
  44. *
  45. * @param string $haystack The string from which to get the first occurrence of $needle.
  46. * @param string $needle The string to find in $haystack.
  47. * @param boolean $part Determines which portion of $haystack this function returns.
  48. * If set to true, it returns all of $haystack from the beginning to the first occurrence of $needle.
  49. * If set to false, it returns all of $haystack from the first occurrence of $needle to the end,
  50. * Default value is false.
  51. * @param string $encoding Character encoding name to use. If it is omitted, internal character encoding is used.
  52. * @return string|boolean The portion of $haystack, or false if $needle is not found.
  53. */
  54. if (!function_exists('mb_stristr')) {
  55. function mb_stristr($haystack, $needle, $part = false, $encoding = null) {
  56. return Multibyte::stristr($haystack, $needle, $part);
  57. }
  58. }
  59. /**
  60. * Get string length.
  61. *
  62. * @param string $string The string being checked for length.
  63. * @param string $encoding Character encoding name to use. If it is omitted, internal character encoding is used.
  64. * @return integer The number of characters in string $string having character encoding encoding.
  65. * A multi-byte character is counted as 1.
  66. */
  67. if (!function_exists('mb_strlen')) {
  68. function mb_strlen($string, $encoding = null) {
  69. return Multibyte::strlen($string);
  70. }
  71. }
  72. /**
  73. * Find position of first occurrence of a string.
  74. *
  75. * @param string $haystack The string being checked.
  76. * @param string $needle The position counted from the beginning of haystack.
  77. * @param integer $offset The search offset. If it is not specified, 0 is used.
  78. * @param string $encoding Character encoding name to use. If it is omitted, internal character encoding is used.
  79. * @return integer|boolean The numeric position of the first occurrence of $needle in the $haystack string.
  80. * If $needle is not found, it returns false.
  81. */
  82. if (!function_exists('mb_strpos')) {
  83. function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) {
  84. return Multibyte::strpos($haystack, $needle, $offset);
  85. }
  86. }
  87. /**
  88. * Finds the last occurrence of a character in a string within another.
  89. *
  90. * @param string $haystack The string from which to get the last occurrence of $needle.
  91. * @param string $needle The string to find in $haystack.
  92. * @param boolean $part Determines which portion of $haystack this function returns.
  93. * If set to true, it returns all of $haystack from the beginning to the last occurrence of $needle.
  94. * If set to false, it returns all of $haystack from the last occurrence of $needle to the end,
  95. * Default value is false.
  96. * @param string $encoding Character encoding name to use. If it is omitted, internal character encoding is used.
  97. * @return string|boolean The portion of $haystack. or false if $needle is not found.
  98. */
  99. if (!function_exists('mb_strrchr')) {
  100. function mb_strrchr($haystack, $needle, $part = false, $encoding = null) {
  101. return Multibyte::strrchr($haystack, $needle, $part);
  102. }
  103. }
  104. /**
  105. * Finds the last occurrence of a character in a string within another, case insensitive.
  106. *
  107. * @param string $haystack The string from which to get the last occurrence of $needle.
  108. * @param string $needle The string to find in $haystack.
  109. * @param boolean $part Determines which portion of $haystack this function returns.
  110. * If set to true, it returns all of $haystack from the beginning to the last occurrence of $needle.
  111. * If set to false, it returns all of $haystack from the last occurrence of $needle to the end,
  112. * Default value is false.
  113. * @param string $encoding Character encoding name to use. If it is omitted, internal character encoding is used.
  114. * @return string|boolean The portion of $haystack. or false if $needle is not found.
  115. */
  116. if (!function_exists('mb_strrichr')) {
  117. function mb_strrichr($haystack, $needle, $part = false, $encoding = null) {
  118. return Multibyte::strrichr($haystack, $needle, $part);
  119. }
  120. }
  121. /**
  122. * Finds position of last occurrence of a string within another, case insensitive
  123. *
  124. * @param string $haystack The string from which to get the position of the last occurrence of $needle.
  125. * @param string $needle The string to find in $haystack.
  126. * @param integer $offset The position in $haystack to start searching.
  127. * @param string $encoding Character encoding name to use. If it is omitted, internal character encoding is used.
  128. * @return integer|boolean The numeric position of the last occurrence of $needle in the $haystack string,
  129. * or false if $needle is not found.
  130. */
  131. if (!function_exists('mb_strripos')) {
  132. function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) {
  133. return Multibyte::strripos($haystack, $needle, $offset);
  134. }
  135. }
  136. /**
  137. * Find position of last occurrence of a string in a string.
  138. *
  139. * @param string $haystack The string being checked, for the last occurrence of $needle.
  140. * @param string $needle The string to find in $haystack.
  141. * @param integer $offset May be specified to begin searching an arbitrary number of characters into the string.
  142. * Negative values will stop searching at an arbitrary point prior to the end of the string.
  143. * @param string $encoding Character encoding name to use. If it is omitted, internal character encoding is used.
  144. * @return integer|boolean The numeric position of the last occurrence of $needle in the $haystack string.
  145. * If $needle is not found, it returns false.
  146. */
  147. if (!function_exists('mb_strrpos')) {
  148. function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) {
  149. return Multibyte::strrpos($haystack, $needle, $offset);
  150. }
  151. }
  152. /**
  153. * Finds first occurrence of a string within another
  154. *
  155. * @param string $haystack The string from which to get the first occurrence of $needle.
  156. * @param string $needle The string to find in $haystack
  157. * @param boolean $part Determines which portion of $haystack this function returns.
  158. * If set to true, it returns all of $haystack from the beginning to the first occurrence of $needle.
  159. * If set to false, it returns all of $haystack from the first occurrence of $needle to the end,
  160. * Default value is FALSE.
  161. * @param string $encoding Character encoding name to use. If it is omitted, internal character encoding is used.
  162. * @return string|boolean The portion of $haystack, or true if $needle is not found.
  163. */
  164. if (!function_exists('mb_strstr')) {
  165. function mb_strstr($haystack, $needle, $part = false, $encoding = null) {
  166. return Multibyte::strstr($haystack, $needle, $part);
  167. }
  168. }
  169. /**
  170. * Make a string lowercase
  171. *
  172. * @param string $string The string being lowercased.
  173. * @param string $encoding Character encoding name to use. If it is omitted, internal character encoding is used.
  174. * @return string with all alphabetic characters converted to lowercase.
  175. */
  176. if (!function_exists('mb_strtolower')) {
  177. function mb_strtolower($string, $encoding = null) {
  178. return Multibyte::strtolower($string);
  179. }
  180. }
  181. /**
  182. * Make a string uppercase
  183. *
  184. * @param string $string The string being uppercased.
  185. * @param string $encoding Character encoding name to use. If it is omitted, internal character encoding is used.
  186. * @return string with all alphabetic characters converted to uppercase.
  187. */
  188. if (!function_exists('mb_strtoupper')) {
  189. function mb_strtoupper($string, $encoding = null) {
  190. return Multibyte::strtoupper($string);
  191. }
  192. }
  193. /**
  194. * Count the number of substring occurrences
  195. *
  196. * @param string $haystack The string being checked.
  197. * @param string $needle The string being found.
  198. * @param string $encoding Character encoding name to use. If it is omitted, internal character encoding is used.
  199. * @return integer The number of times the $needle substring occurs in the $haystack string.
  200. */
  201. if (!function_exists('mb_substr_count')) {
  202. function mb_substr_count($haystack, $needle, $encoding = null) {
  203. return Multibyte::substrCount($haystack, $needle);
  204. }
  205. }
  206. /**
  207. * Get part of string
  208. *
  209. * @param string $string The string being checked.
  210. * @param integer $start The first position used in $string.
  211. * @param integer $length The maximum length of the returned string.
  212. * @param string $encoding Character encoding name to use. If it is omitted, internal character encoding is used.
  213. * @return string The portion of $string specified by the $string and $length parameters.
  214. */
  215. if (!function_exists('mb_substr')) {
  216. function mb_substr($string, $start, $length = null, $encoding = null) {
  217. return Multibyte::substr($string, $start, $length);
  218. }
  219. }
  220. /**
  221. * Encode string for MIME header
  222. *
  223. * @param string $str The string being encoded
  224. * @param string $charset specifies the name of the character set in which str is represented in.
  225. * The default value is determined by the current NLS setting (mbstring.language).
  226. * @param string $transfer_encoding specifies the scheme of MIME encoding.
  227. * It should be either "B" (Base64) or "Q" (Quoted-Printable). Falls back to "B" if not given.
  228. * @param string $linefeed specifies the EOL (end-of-line) marker with which
  229. * mb_encode_mimeheader() performs line-folding
  230. * (a Âť RFC term, the act of breaking a line longer than a certain length into multiple lines.
  231. * The length is currently hard-coded to 74 characters). Falls back to "\r\n" (CRLF) if not given.
  232. * @param integer $indent [definition unknown and appears to have no affect]
  233. * @return string A converted version of the string represented in ASCII.
  234. */
  235. if (!function_exists('mb_encode_mimeheader')) {
  236. function mb_encode_mimeheader($str, $charset = 'UTF-8', $transfer_encoding = 'B', $linefeed = "\r\n", $indent = 1) {
  237. return Multibyte::mimeEncode($str, $charset, $linefeed);
  238. }
  239. }
  240. /**
  241. * Multibyte handling methods.
  242. *
  243. *
  244. * @package cake
  245. * @subpackage cake.cake.libs
  246. */
  247. class Multibyte extends Object {
  248. /**
  249. * Holds the case folding values
  250. *
  251. * @var array
  252. * @access private
  253. */
  254. private static $__caseFold = array();
  255. /**
  256. * Holds an array of Unicode code point ranges
  257. *
  258. * @var array
  259. * @access private
  260. */
  261. private static $__codeRange = array();
  262. /**
  263. * Holds the current code point range
  264. *
  265. * @var string
  266. * @access private
  267. */
  268. private static $__table = null;
  269. /**
  270. * Converts a multibyte character string
  271. * to the decimal value of the character
  272. *
  273. * @param multibyte string $string
  274. * @return array
  275. * @access public
  276. * @static
  277. */
  278. public static function utf8($string) {
  279. $map = array();
  280. $values = array();
  281. $find = 1;
  282. $length = strlen($string);
  283. for ($i = 0; $i < $length; $i++) {
  284. $value = ord($string[$i]);
  285. if ($value < 128) {
  286. $map[] = $value;
  287. } else {
  288. if (empty($values)) {
  289. $find = ($value < 224) ? 2 : 3;
  290. }
  291. $values[] = $value;
  292. if (count($values) === $find) {
  293. if ($find == 3) {
  294. $map[] = (($values[0] % 16) * 4096) + (($values[1] % 64) * 64) + ($values[2] % 64);
  295. } else {
  296. $map[] = (($values[0] % 32) * 64) + ($values[1] % 64);
  297. }
  298. $values = array();
  299. $find = 1;
  300. }
  301. }
  302. }
  303. return $map;
  304. }
  305. /**
  306. * Converts the decimal value of a multibyte character string
  307. * to a string
  308. *
  309. * @param array $array
  310. * @return string
  311. * @access public
  312. * @static
  313. */
  314. public static function ascii($array) {
  315. $ascii = '';
  316. foreach ($array as $utf8) {
  317. if ($utf8 < 128) {
  318. $ascii .= chr($utf8);
  319. } elseif ($utf8 < 2048) {
  320. $ascii .= chr(192 + (($utf8 - ($utf8 % 64)) / 64));
  321. $ascii .= chr(128 + ($utf8 % 64));
  322. } else {
  323. $ascii .= chr(224 + (($utf8 - ($utf8 % 4096)) / 4096));
  324. $ascii .= chr(128 + ((($utf8 % 4096) - ($utf8 % 64)) / 64));
  325. $ascii .= chr(128 + ($utf8 % 64));
  326. }
  327. }
  328. return $ascii;
  329. }
  330. /**
  331. * Find position of first occurrence of a case-insensitive string.
  332. *
  333. * @param multi-byte string $haystack The string from which to get the position of the first occurrence of $needle.
  334. * @param multi-byte string $needle The string to find in $haystack.
  335. * @param integer $offset The position in $haystack to start searching.
  336. * @return integer|boolean The numeric position of the first occurrence of $needle in the $haystack string,
  337. * or false if $needle is not found.
  338. * @access public
  339. * @static
  340. */
  341. public static function stripos($haystack, $needle, $offset = 0) {
  342. if (self::checkMultibyte($haystack)) {
  343. $haystack = self::strtoupper($haystack);
  344. $needle = self::strtoupper($needle);
  345. return self::strpos($haystack, $needle, $offset);
  346. }
  347. return stripos($haystack, $needle, $offset);
  348. }
  349. /**
  350. * Finds first occurrence of a string within another, case insensitive.
  351. *
  352. * @param string $haystack The string from which to get the first occurrence of $needle.
  353. * @param string $needle The string to find in $haystack.
  354. * @param boolean $part Determines which portion of $haystack this function returns.
  355. * If set to true, it returns all of $haystack from the beginning to the first occurrence of $needle.
  356. * If set to false, it returns all of $haystack from the first occurrence of $needle to the end,
  357. * Default value is false.
  358. * @return int|boolean The portion of $haystack, or false if $needle is not found.
  359. * @access public
  360. * @static
  361. */
  362. public static function stristr($haystack, $needle, $part = false) {
  363. $php = (PHP_VERSION < 5.3);
  364. if (($php && $part) || self::checkMultibyte($haystack)) {
  365. $check = self::strtoupper($haystack);
  366. $check = self::utf8($check);
  367. $found = false;
  368. $haystack = self::utf8($haystack);
  369. $haystackCount = count($haystack);
  370. $needle = self::strtoupper($needle);
  371. $needle = self::utf8($needle);
  372. $needleCount = count($needle);
  373. $parts = array();
  374. $position = 0;
  375. while (($found === false) && ($position < $haystackCount)) {
  376. if (isset($needle[0]) && $needle[0] === $check[$position]) {
  377. for ($i = 1; $i < $needleCount; $i++) {
  378. if ($needle[$i] !== $check[$position + $i]) {
  379. break;
  380. }
  381. }
  382. if ($i === $needleCount) {
  383. $found = true;
  384. }
  385. }
  386. if (!$found) {
  387. $parts[] = $haystack[$position];
  388. unset($haystack[$position]);
  389. }
  390. $position++;
  391. }
  392. if ($found && $part && !empty($parts)) {
  393. return self::ascii($parts);
  394. } elseif ($found && !empty($haystack)) {
  395. return self::ascii($haystack);
  396. }
  397. return false;
  398. }
  399. if (!$php) {
  400. return stristr($haystack, $needle, $part);
  401. }
  402. return stristr($haystack, $needle);
  403. }
  404. /**
  405. * Get string length.
  406. *
  407. * @param string $string The string being checked for length.
  408. * @return integer The number of characters in string $string
  409. * @access public
  410. * @static
  411. */
  412. public static function strlen($string) {
  413. if (self::checkMultibyte($string)) {
  414. $string = self::utf8($string);
  415. return count($string);
  416. }
  417. return strlen($string);
  418. }
  419. /**
  420. * Find position of first occurrence of a string.
  421. *
  422. * @param string $haystack The string being checked.
  423. * @param string $needle The position counted from the beginning of haystack.
  424. * @param integer $offset The search offset. If it is not specified, 0 is used.
  425. * @return integer|boolean The numeric position of the first occurrence of $needle in the $haystack string.
  426. * If $needle is not found, it returns false.
  427. * @access public
  428. * @static
  429. */
  430. public static function strpos($haystack, $needle, $offset = 0) {
  431. if (self::checkMultibyte($haystack)) {
  432. $found = false;
  433. $haystack = self::utf8($haystack);
  434. $haystackCount = count($haystack);
  435. $needle = self::utf8($needle);
  436. $needleCount = count($needle);
  437. $position = $offset;
  438. while (($found === false) && ($position < $haystackCount)) {
  439. if (isset($needle[0]) && $needle[0] === $haystack[$position]) {
  440. for ($i = 1; $i < $needleCount; $i++) {
  441. if ($needle[$i] !== $haystack[$position + $i]) {
  442. break;
  443. }
  444. }
  445. if ($i === $needleCount) {
  446. $found = true;
  447. $position--;
  448. }
  449. }
  450. $position++;
  451. }
  452. if ($found) {
  453. return $position;
  454. }
  455. return false;
  456. }
  457. return strpos($haystack, $needle, $offset);
  458. }
  459. /**
  460. * Finds the last occurrence of a character in a string within another.
  461. *
  462. * @param string $haystack The string from which to get the last occurrence of $needle.
  463. * @param string $needle The string to find in $haystack.
  464. * @param boolean $part Determines which portion of $haystack this function returns.
  465. * If set to true, it returns all of $haystack from the beginning to the last occurrence of $needle.
  466. * If set to false, it returns all of $haystack from the last occurrence of $needle to the end,
  467. * Default value is false.
  468. * @return string|boolean The portion of $haystack. or false if $needle is not found.
  469. * @access public
  470. * @static
  471. */
  472. public static function strrchr($haystack, $needle, $part = false) {
  473. $check = self::utf8($haystack);
  474. $found = false;
  475. $haystack = self::utf8($haystack);
  476. $haystackCount = count($haystack);
  477. $matches = array_count_values($check);
  478. $needle = self::utf8($needle);
  479. $needleCount = count($needle);
  480. $parts = array();
  481. $position = 0;
  482. while (($found === false) && ($position < $haystackCount)) {
  483. if (isset($needle[0]) && $needle[0] === $check[$position]) {
  484. for ($i = 1; $i < $needleCount; $i++) {
  485. if ($needle[$i] !== $check[$position + $i]) {
  486. if ($needle[$i] === $check[($position + $i) -1]) {
  487. $found = true;
  488. }
  489. unset($parts[$position - 1]);
  490. $haystack = array_merge(array($haystack[$position]), $haystack);
  491. break;
  492. }
  493. }
  494. if (isset($matches[$needle[0]]) && $matches[$needle[0]] > 1) {
  495. $matches[$needle[0]] = $matches[$needle[0]] - 1;
  496. } elseif ($i === $needleCount) {
  497. $found = true;
  498. }
  499. }
  500. if (!$found && isset($haystack[$position])) {
  501. $parts[] = $haystack[$position];
  502. unset($haystack[$position]);
  503. }
  504. $position++;
  505. }
  506. if ($found && $part && !empty($parts)) {
  507. return self::ascii($parts);
  508. } elseif ($found && !empty($haystack)) {
  509. return self::ascii($haystack);
  510. }
  511. return false;
  512. }
  513. /**
  514. * Finds the last occurrence of a character in a string within another, case insensitive.
  515. *
  516. * @param string $haystack The string from which to get the last occurrence of $needle.
  517. * @param string $needle The string to find in $haystack.
  518. * @param boolean $part Determines which portion of $haystack this function returns.
  519. * If set to true, it returns all of $haystack from the beginning to the last occurrence of $needle.
  520. * If set to false, it returns all of $haystack from the last occurrence of $needle to the end,
  521. * Default value is false.
  522. * @return string|boolean The portion of $haystack. or false if $needle is not found.
  523. * @access public
  524. * @static
  525. */
  526. public static function strrichr($haystack, $needle, $part = false) {
  527. $check = self::strtoupper($haystack);
  528. $check = self::utf8($check);
  529. $found = false;
  530. $haystack = self::utf8($haystack);
  531. $haystackCount = count($haystack);
  532. $matches = array_count_values($check);
  533. $needle = self::strtoupper($needle);
  534. $needle = self::utf8($needle);
  535. $needleCount = count($needle);
  536. $parts = array();
  537. $position = 0;
  538. while (($found === false) && ($position < $haystackCount)) {
  539. if (isset($needle[0]) && $needle[0] === $check[$position]) {
  540. for ($i = 1; $i < $needleCount; $i++) {
  541. if ($needle[$i] !== $check[$position + $i]) {
  542. if ($needle[$i] === $check[($position + $i) -1]) {
  543. $found = true;
  544. }
  545. unset($parts[$position - 1]);
  546. $haystack = array_merge(array($haystack[$position]), $haystack);
  547. break;
  548. }
  549. }
  550. if (isset($matches[$needle[0]]) && $matches[$needle[0]] > 1) {
  551. $matches[$needle[0]] = $matches[$needle[0]] - 1;
  552. } elseif ($i === $needleCount) {
  553. $found = true;
  554. }
  555. }
  556. if (!$found && isset($haystack[$position])) {
  557. $parts[] = $haystack[$position];
  558. unset($haystack[$position]);
  559. }
  560. $position++;
  561. }
  562. if ($found && $part && !empty($parts)) {
  563. return self::ascii($parts);
  564. } elseif ($found && !empty($haystack)) {
  565. return self::ascii($haystack);
  566. }
  567. return false;
  568. }
  569. /**
  570. * Finds position of last occurrence of a string within another, case insensitive
  571. *
  572. * @param string $haystack The string from which to get the position of the last occurrence of $needle.
  573. * @param string $needle The string to find in $haystack.
  574. * @param integer $offset The position in $haystack to start searching.
  575. * @return integer|boolean The numeric position of the last occurrence of $needle in the $haystack string,
  576. * or false if $needle is not found.
  577. * @access public
  578. * @static
  579. */
  580. public static function strripos($haystack, $needle, $offset = 0) {
  581. if (self::checkMultibyte($haystack)) {
  582. $found = false;
  583. $haystack = self::strtoupper($haystack);
  584. $haystack = self::utf8($haystack);
  585. $haystackCount = count($haystack);
  586. $matches = array_count_values($haystack);
  587. $needle = self::strtoupper($needle);
  588. $needle = self::utf8($needle);
  589. $needleCount = count($needle);
  590. $position = $offset;
  591. while (($found === false) && ($position < $haystackCount)) {
  592. if (isset($needle[0]) && $needle[0] === $haystack[$position]) {
  593. for ($i = 1; $i < $needleCount; $i++) {
  594. if ($needle[$i] !== $haystack[$position + $i]) {
  595. if ($needle[$i] === $haystack[($position + $i) -1]) {
  596. $position--;
  597. $found = true;
  598. continue;
  599. }
  600. }
  601. }
  602. if (!$offset && isset($matches[$needle[0]]) && $matches[$needle[0]] > 1) {
  603. $matches[$needle[0]] = $matches[$needle[0]] - 1;
  604. } elseif ($i === $needleCount) {
  605. $found = true;
  606. $position--;
  607. }
  608. }
  609. $position++;
  610. }
  611. return ($found) ? $position : false;
  612. }
  613. return strripos($haystack, $needle, $offset);
  614. }
  615. /**
  616. * Find position of last occurrence of a string in a string.
  617. *
  618. * @param string $haystack The string being checked, for the last occurrence of $needle.
  619. * @param string $needle The string to find in $haystack.
  620. * @param integer $offset May be specified to begin searching an arbitrary number of characters into the string.
  621. * Negative values will stop searching at an arbitrary point prior to the end of the string.
  622. * @return integer|boolean The numeric position of the last occurrence of $needle in the $haystack string.
  623. * If $needle is not found, it returns false.
  624. * @access public
  625. * @static
  626. */
  627. public static function strrpos($haystack, $needle, $offset = 0) {
  628. if (self::checkMultibyte($haystack)) {
  629. $found = false;
  630. $haystack = self::utf8($haystack);
  631. $haystackCount = count($haystack);
  632. $matches = array_count_values($haystack);
  633. $needle = self::utf8($needle);
  634. $needleCount = count($needle);
  635. $position = $offset;
  636. while (($found === false) && ($position < $haystackCount)) {
  637. if (isset($needle[0]) && $needle[0] === $haystack[$position]) {
  638. for ($i = 1; $i < $needleCount; $i++) {
  639. if ($needle[$i] !== $haystack[$position + $i]) {
  640. if ($needle[$i] === $haystack[($position + $i) -1]) {
  641. $position--;
  642. $found = true;
  643. continue;
  644. }
  645. }
  646. }
  647. if (!$offset && isset($matches[$needle[0]]) && $matches[$needle[0]] > 1) {
  648. $matches[$needle[0]] = $matches[$needle[0]] - 1;
  649. } elseif ($i === $needleCount) {
  650. $found = true;
  651. $position--;
  652. }
  653. }
  654. $position++;
  655. }
  656. return ($found) ? $position : false;
  657. }
  658. return strrpos($haystack, $needle, $offset);
  659. }
  660. /**
  661. * Finds first occurrence of a string within another
  662. *
  663. * @param string $haystack The string from which to get the first occurrence of $needle.
  664. * @param string $needle The string to find in $haystack
  665. * @param boolean $part Determines which portion of $haystack this function returns.
  666. * If set to true, it returns all of $haystack from the beginning to the first occurrence of $needle.
  667. * If set to false, it returns all of $haystack from the first occurrence of $needle to the end,
  668. * Default value is FALSE.
  669. * @return string|boolean The portion of $haystack, or true if $needle is not found.
  670. * @access public
  671. * @static
  672. */
  673. public static function strstr($haystack, $needle, $part = false) {
  674. $php = (PHP_VERSION < 5.3);
  675. if (($php && $part) || self::checkMultibyte($haystack)) {
  676. $check = self::utf8($haystack);
  677. $found = false;
  678. $haystack = self::utf8($haystack);
  679. $haystackCount = count($haystack);
  680. $needle = self::utf8($needle);
  681. $needleCount = count($needle);
  682. $parts = array();
  683. $position = 0;
  684. while (($found === false) && ($position < $haystackCount)) {
  685. if (isset($needle[0]) && $needle[0] === $check[$position]) {
  686. for ($i = 1; $i < $needleCount; $i++) {
  687. if ($needle[$i] !== $check[$position + $i]) {
  688. break;
  689. }
  690. }
  691. if ($i === $needleCount) {
  692. $found = true;
  693. }
  694. }
  695. if (!$found) {
  696. $parts[] = $haystack[$position];
  697. unset($haystack[$position]);
  698. }
  699. $position++;
  700. }
  701. if ($found && $part && !empty($parts)) {
  702. return self::ascii($parts);
  703. } elseif ($found && !empty($haystack)) {
  704. return self::ascii($haystack);
  705. }
  706. return false;
  707. }
  708. if (!$php) {
  709. return strstr($haystack, $needle, $part);
  710. }
  711. return strstr($haystack, $needle);
  712. }
  713. /**
  714. * Make a string lowercase
  715. *
  716. * @param string $string The string being lowercased.
  717. * @return string with all alphabetic characters converted to lowercase.
  718. * @access public
  719. * @static
  720. */
  721. public static function strtolower($string) {
  722. $utf8Map = self::utf8($string);
  723. $length = count($utf8Map);
  724. $lowerCase = array();
  725. $matched = false;
  726. for ($i = 0 ; $i < $length; $i++) {
  727. $char = $utf8Map[$i];
  728. if ($char < 128) {
  729. $str = strtolower(chr($char));
  730. $strlen = strlen($str);
  731. for ($ii = 0 ; $ii < $strlen; $ii++) {
  732. $lower = ord(substr($str, $ii, 1));
  733. }
  734. $lowerCase[] = $lower;
  735. $matched = true;
  736. } else {
  737. $matched = false;
  738. $keys = self::__find($char, 'upper');
  739. if (!empty($keys)) {
  740. foreach ($keys as $key => $value) {
  741. if ($keys[$key]['upper'] == $char && count($keys[$key]['lower'][0]) === 1) {
  742. $lowerCase[] = $keys[$key]['lower'][0];
  743. $matched = true;
  744. break 1;
  745. }
  746. }
  747. }
  748. }
  749. if ($matched === false) {
  750. $lowerCase[] = $char;
  751. }
  752. }
  753. return self::ascii($lowerCase);
  754. }
  755. /**
  756. * Make a string uppercase
  757. *
  758. * @param string $string The string being uppercased.
  759. * @param string $encoding Character encoding name to use. If it is omitted, internal character encoding is used.
  760. * @return string with all alphabetic characters converted to uppercase.
  761. * @access public
  762. * @static
  763. */
  764. public static function strtoupper($string) {
  765. $utf8Map = self::utf8($string);
  766. $length = count($utf8Map);
  767. $matched = false;
  768. $replaced = array();
  769. $upperCase = array();
  770. for ($i = 0 ; $i < $length; $i++) {
  771. $char = $utf8Map[$i];
  772. if ($char < 128) {
  773. $str = strtoupper(chr($char));
  774. $strlen = strlen($str);
  775. for ($ii = 0 ; $ii < $strlen; $ii++) {
  776. $upper = ord(substr($str, $ii, 1));
  777. }
  778. $upperCase[] = $upper;
  779. $matched = true;
  780. } else {
  781. $matched = false;
  782. $keys = self::__find($char);
  783. $keyCount = count($keys);
  784. if (!empty($keys)) {
  785. foreach ($keys as $key => $value) {
  786. $matched = false;
  787. $replace = 0;
  788. if ($length > 1 && count($keys[$key]['lower']) > 1) {
  789. $j = 0;
  790. for ($ii = 0, $count = count($keys[$key]['lower']); $ii < $count; $ii++) {
  791. $nextChar = $utf8Map[$i + $ii];
  792. if (isset($nextChar) && ($nextChar == $keys[$key]['lower'][$j + $ii])) {
  793. $replace++;
  794. }
  795. }
  796. if ($replace == $count) {
  797. $upperCase[] = $keys[$key]['upper'];
  798. $replaced = array_merge($replaced, array_values($keys[$key]['lower']));
  799. $matched = true;
  800. break 1;
  801. }
  802. } elseif ($length > 1 && $keyCount > 1) {
  803. $j = 0;
  804. for ($ii = 1; $ii < $keyCount; $ii++) {
  805. $nextChar = $utf8Map[$i + $ii - 1];
  806. if (in_array($nextChar, $keys[$ii]['lower'])) {
  807. for ($jj = 0, $count = count($keys[$ii]['lower']); $jj < $count; $jj++) {
  808. $nextChar = $utf8Map[$i + $jj];
  809. if (isset($nextChar) && ($nextChar == $keys[$ii]['lower'][$j + $jj])) {
  810. $replace++;
  811. }
  812. }
  813. if ($replace == $count) {
  814. $upperCase[] = $keys[$ii]['upper'];
  815. $replaced = array_merge($replaced, array_values($keys[$ii]['lower']));
  816. $matched = true;
  817. break 2;
  818. }
  819. }
  820. }
  821. }
  822. if ($keys[$key]['lower'][0] == $char) {
  823. $upperCase[] = $keys[$key]['upper'];
  824. $matched = true;
  825. break 1;
  826. }
  827. }
  828. }
  829. }
  830. if ($matched === false && !in_array($char, $replaced, true)) {
  831. $upperCase[] = $char;
  832. }
  833. }
  834. return self::ascii($upperCase);
  835. }
  836. /**
  837. * Count the number of substring occurrences
  838. *
  839. * @param string $haystack The string being checked.
  840. * @param string $needle The string being found.
  841. * @return integer The number of times the $needle substring occurs in the $haystack string.
  842. * @access public
  843. * @static
  844. */
  845. public static function substrCount($haystack, $needle) {
  846. $count = 0;
  847. $haystack = self::utf8($haystack);
  848. $haystackCount = count($haystack);
  849. $matches = array_count_values($haystack);
  850. $needle = self::utf8($needle);
  851. $needleCount = count($needle);
  852. if ($needleCount === 1 && isset($matches[$needle[0]])) {
  853. return $matches[$needle[0]];
  854. }
  855. for ($i = 0; $i < $haystackCount; $i++) {
  856. if (isset($needle[0]) && $needle[0] === $haystack[$i]) {
  857. for ($ii = 1; $ii < $needleCount; $ii++) {
  858. if ($needle[$ii] === $haystack[$i + 1]) {
  859. if ((isset($needle[$ii + 1]) && $haystack[$i + 2]) && $needle[$ii + 1] !== $haystack[$i + 2]) {
  860. $count--;
  861. } else {
  862. $count++;
  863. }
  864. }
  865. }
  866. }
  867. }
  868. return $count;
  869. }
  870. /**
  871. * Get part of string
  872. *
  873. * @param string $string The string being checked.
  874. * @param integer $start The first position used in $string.
  875. * @param integer $length The maximum length of the returned string.
  876. * @return string The portion of $string specified by the $string and $length parameters.
  877. * @access public
  878. * @static
  879. */
  880. public static function substr($string, $start, $length = null) {
  881. if ($start === 0 && $length === null) {
  882. return $string;
  883. }
  884. $string = self::utf8($string);
  885. $stringCount = count($string);
  886. for ($i = 1; $i <= $start; $i++) {
  887. unset($string[$i - 1]);
  888. }
  889. if ($length === null || count($string) < $length) {
  890. return self::ascii($string);
  891. }
  892. $string = array_values($string);
  893. $value = array();
  894. for ($i = 0; $i < $length; $i++) {
  895. $value[] = $string[$i];
  896. }
  897. return self::ascii($value);
  898. }
  899. /**
  900. * Prepare a string for mail transport, using the provided encoding
  901. *
  902. * @param string $string value to encode
  903. * @param string $charset charset to use for encoding. defaults to UTF-8
  904. * @param string $newline
  905. * @return string
  906. * @access public
  907. * @static
  908. * @TODO: add support for 'Q'('Quoted Printable') encoding
  909. */
  910. public static function mimeEncode($string, $charset = null, $newline = "\r\n") {
  911. if (!self::checkMultibyte($string) && strlen($string) < 75) {
  912. return $string;
  913. }
  914. if (empty($charset)) {
  915. $charset = Configure::read('App.encoding');
  916. }
  917. $charset = strtoupper($charset);
  918. $start = '=?' . $charset . '?B?';
  919. $end = '?=';
  920. $spacer = $end . $newline . ' ' . $start;
  921. $length = 75 - strlen($start) - strlen($end);
  922. $length = $length - ($length % 4);
  923. if ($charset == 'UTF-8') {
  924. $parts = array();
  925. $maxchars = floor(($length * 3) / 4);
  926. while (strlen($string) > $maxchars) {
  927. $i = $maxchars;
  928. $test = ord($string[$i]);
  929. while ($test >= 128 && $test <= 191) {
  930. $i--;
  931. $test = ord($string[$i]);
  932. }
  933. $parts[] = base64_encode(substr($string, 0, $i));
  934. $string = substr($string, $i);
  935. }
  936. $parts[] = base64_encode($string);
  937. $string = implode($spacer, $parts);
  938. } else {
  939. $string = chunk_split(base64_encode($string), $length, $spacer);
  940. $string = preg_replace('/' . preg_quote($spacer) . '$/', '', $string);
  941. }
  942. return $start . $string . $end;
  943. }
  944. /**
  945. * Return the Code points range for Unicode characters
  946. *
  947. * @param interger $decimal
  948. * @return string
  949. * @access private
  950. */
  951. private static function __codepoint($decimal) {
  952. if ($decimal > 128 && $decimal < 256) {
  953. $return = '0080_00ff'; // Latin-1 Supplement
  954. } elseif ($decimal < 384) {
  955. $return = '0100_017f'; // Latin Extended-A
  956. } elseif ($decimal < 592) {
  957. $return = '0180_024F'; // Latin Extended-B
  958. } elseif ($decimal < 688) {
  959. $return = '0250_02af'; // IPA Extensions
  960. } elseif ($decimal >= 880 && $decimal < 1024) {
  961. $return = '0370_03ff'; // Greek and Coptic
  962. } elseif ($decimal < 1280) {
  963. $return = '0400_04ff'; // Cyrillic
  964. } elseif ($decimal < 1328) {
  965. $return = '0500_052f'; // Cyrillic Supplement
  966. } elseif ($decimal < 1424) {
  967. $return = '0530_058f'; // Armenian
  968. } elseif ($decimal >= 7680 && $decimal < 7936) {
  969. $return = '1e00_1eff'; // Latin Extended Additional
  970. } elseif ($decimal < 8192) {
  971. $return = '1f00_1fff'; // Greek Extended
  972. } elseif ($decimal >= 8448 && $decimal < 8528) {
  973. $return = '2100_214f'; // Letterlike Symbols
  974. } elseif ($decimal < 8592) {
  975. $return = '2150_218f'; // Number Forms
  976. } elseif ($decimal >= 9312 && $decimal < 9472) {
  977. $return = '2460_24ff'; // Enclosed Alphanumerics
  978. } elseif ($decimal >= 11264 && $decimal < 11360) {
  979. $return = '2c00_2c5f'; // Glagolitic
  980. } elseif ($decimal < 11392) {
  981. $return = '2c60_2c7f'; // Latin Extended-C
  982. } elseif ($decimal < 11520) {
  983. $return = '2c80_2cff'; // Coptic
  984. } elseif ($decimal >= 65280 && $decimal < 65520) {
  985. $return = 'ff00_ffef'; // Halfwidth and Fullwidth Forms
  986. } else {
  987. $return = false;
  988. }
  989. self::$__codeRange[$decimal] = $return;
  990. return $return;
  991. }
  992. /**
  993. * Find the related code folding values for $char
  994. *
  995. * @param integer $char decimal value of character
  996. * @param string $type
  997. * @return array
  998. * @access private
  999. */
  1000. private static function __find($char, $type = 'lower') {
  1001. $value = false;
  1002. $found = array();
  1003. if (!isset(self::$__codeRange[$char])) {
  1004. $range = self::__codepoint($char);
  1005. if ($range === false) {
  1006. return null;
  1007. }
  1008. Configure::load('unicode' . DS . 'casefolding' . DS . $range);
  1009. self::$__caseFold[$range] = Configure::read($range);
  1010. Configure::delete($range);
  1011. }
  1012. if (!self::$__codeRange[$char]) {
  1013. return null;
  1014. }
  1015. self::$__table = self::$__codeRange[$char];
  1016. $count = count(self::$__caseFold[self::$__table]);
  1017. for ($i = 0; $i < $count; $i++) {
  1018. if ($type === 'lower' && self::$__caseFold[self::$__table][$i][$type][0] === $char) {
  1019. $found[] = self::$__caseFold[self::$__table][$i];
  1020. } elseif ($type === 'upper' && self::$__caseFold[self::$__table][$i][$type] === $char) {
  1021. $found[] = self::$__caseFold[self::$__table][$i];
  1022. }
  1023. }
  1024. return $found;
  1025. }
  1026. /**
  1027. * Check the $string for multibyte characters
  1028. * @param string $string value to test
  1029. * @return boolean
  1030. * @access public
  1031. * @static
  1032. */
  1033. public static function checkMultibyte($string) {
  1034. $length = strlen($string);
  1035. for ($i = 0; $i < $length; $i++ ) {
  1036. $value = ord(($string[$i]));
  1037. if ($value > 128) {
  1038. return true;
  1039. }
  1040. }
  1041. return false;
  1042. }
  1043. }
  1044. ?>