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