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