1<?php
2/**
3 * Multibyte handling methods.
4 *
5 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
6 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
7 *
8 * Licensed under The MIT License
9 * For full copyright and license information, please see the LICENSE.txt
10 * Redistributions of files must retain the above copyright notice.
11 *
12 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
13 * @link          https://cakephp.org CakePHP(tm) Project
14 * @package       Cake.I18n
15 * @since         CakePHP(tm) v 1.2.0.6833
16 * @license       https://opensource.org/licenses/mit-license.php MIT License
17 */
18
19/**
20 * Multibyte handling methods.
21 *
22 * @package       Cake.I18n
23 */
24class Multibyte {
25
26/**
27 *  Holds the case folding values
28 *
29 * @var array
30 */
31	protected static $_caseFold = array();
32
33/**
34 * Holds an array of Unicode code point ranges
35 *
36 * @var array
37 */
38	protected static $_codeRange = array();
39
40/**
41 * Holds the current code point range
42 *
43 * @var string
44 */
45	protected static $_table = null;
46
47/**
48 * Converts a multibyte character string
49 * to the decimal value of the character
50 *
51 * @param string $string String to convert.
52 * @return array
53 */
54	public static function utf8($string) {
55		$map = array();
56
57		$values = array();
58		$find = 1;
59		$length = strlen($string);
60
61		for ($i = 0; $i < $length; $i++) {
62			$value = ord($string[$i]);
63
64			if ($value < 128) {
65				$map[] = $value;
66			} else {
67				if (empty($values)) {
68					$find = ($value < 224) ? 2 : 3;
69				}
70				$values[] = $value;
71
72				if (count($values) === $find) {
73					if ($find == 3) {
74						$map[] = (($values[0] % 16) * 4096) + (($values[1] % 64) * 64) + ($values[2] % 64);
75					} else {
76						$map[] = (($values[0] % 32) * 64) + ($values[1] % 64);
77					}
78					$values = array();
79					$find = 1;
80				}
81			}
82		}
83		return $map;
84	}
85
86/**
87 * Converts the decimal value of a multibyte character string
88 * to a string
89 *
90 * @param array $array Values array.
91 * @return string
92 */
93	public static function ascii($array) {
94		$ascii = '';
95
96		foreach ($array as $utf8) {
97			if ($utf8 < 128) {
98				$ascii .= chr($utf8);
99			} elseif ($utf8 < 2048) {
100				$ascii .= chr(192 + (($utf8 - ($utf8 % 64)) / 64));
101				$ascii .= chr(128 + ($utf8 % 64));
102			} else {
103				$ascii .= chr(224 + (($utf8 - ($utf8 % 4096)) / 4096));
104				$ascii .= chr(128 + ((($utf8 % 4096) - ($utf8 % 64)) / 64));
105				$ascii .= chr(128 + ($utf8 % 64));
106			}
107		}
108		return $ascii;
109	}
110
111/**
112 * Find position of first occurrence of a case-insensitive string.
113 *
114 * @param string $haystack The string from which to get the position of the first occurrence of $needle.
115 * @param string $needle The string to find in $haystack.
116 * @param int $offset The position in $haystack to start searching.
117 * @return int|bool The numeric position of the first occurrence of $needle in the $haystack string,
118 *    or false if $needle is not found.
119 */
120	public static function stripos($haystack, $needle, $offset = 0) {
121		if (Multibyte::checkMultibyte($haystack)) {
122			$haystack = Multibyte::strtoupper($haystack);
123			$needle = Multibyte::strtoupper($needle);
124			return Multibyte::strpos($haystack, $needle, $offset);
125		}
126		return stripos($haystack, $needle, $offset);
127	}
128
129/**
130 * Finds first occurrence of a string within another, case insensitive.
131 *
132 * @param string $haystack The string from which to get the first occurrence of $needle.
133 * @param string $needle The string to find in $haystack.
134 * @param bool $part Determines which portion of $haystack this function returns.
135 *    If set to true, it returns all of $haystack from the beginning to the first occurrence of $needle.
136 *    If set to false, it returns all of $haystack from the first occurrence of $needle to the end,
137 *    Default value is false.
138 * @return int|bool The portion of $haystack, or false if $needle is not found.
139 */
140	public static function stristr($haystack, $needle, $part = false) {
141		$php = (PHP_VERSION < 5.3);
142
143		if (($php && $part) || Multibyte::checkMultibyte($haystack)) {
144			$check = Multibyte::strtoupper($haystack);
145			$check = Multibyte::utf8($check);
146			$found = false;
147
148			$haystack = Multibyte::utf8($haystack);
149			$haystackCount = count($haystack);
150
151			$needle = Multibyte::strtoupper($needle);
152			$needle = Multibyte::utf8($needle);
153			$needleCount = count($needle);
154
155			$parts = array();
156			$position = 0;
157
158			while (($found === false) && ($position < $haystackCount)) {
159				if (isset($needle[0]) && $needle[0] === $check[$position]) {
160					for ($i = 1; $i < $needleCount; $i++) {
161						if ($needle[$i] !== $check[$position + $i]) {
162							break;
163						}
164					}
165					if ($i === $needleCount) {
166						$found = true;
167					}
168				}
169				if (!$found) {
170					$parts[] = $haystack[$position];
171					unset($haystack[$position]);
172				}
173				$position++;
174			}
175
176			if ($found && $part && !empty($parts)) {
177				return Multibyte::ascii($parts);
178			} elseif ($found && !empty($haystack)) {
179				return Multibyte::ascii($haystack);
180			}
181			return false;
182		}
183
184		if (!$php) {
185			return stristr($haystack, $needle, $part);
186		}
187		return stristr($haystack, $needle);
188	}
189
190/**
191 * Get string length.
192 *
193 * @param string $string The string being checked for length.
194 * @return int The number of characters in string $string
195 */
196	public static function strlen($string) {
197		if (Multibyte::checkMultibyte($string)) {
198			$string = Multibyte::utf8($string);
199			return count($string);
200		}
201		return strlen($string);
202	}
203
204/**
205 * Find position of first occurrence of a string.
206 *
207 * @param string $haystack The string being checked.
208 * @param string $needle The position counted from the beginning of haystack.
209 * @param int $offset The search offset. If it is not specified, 0 is used.
210 * @return int|bool The numeric position of the first occurrence of $needle in the $haystack string.
211 *    If $needle is not found, it returns false.
212 */
213	public static function strpos($haystack, $needle, $offset = 0) {
214		if (Multibyte::checkMultibyte($haystack)) {
215			$found = false;
216
217			$haystack = Multibyte::utf8($haystack);
218			$haystackCount = count($haystack);
219
220			$needle = Multibyte::utf8($needle);
221			$needleCount = count($needle);
222
223			$position = $offset;
224
225			while (($found === false) && ($position < $haystackCount)) {
226				if (isset($needle[0]) && $needle[0] === $haystack[$position]) {
227					for ($i = 1; $i < $needleCount; $i++) {
228						if ($needle[$i] !== $haystack[$position + $i]) {
229							break;
230						}
231					}
232					if ($i === $needleCount) {
233						$found = true;
234						$position--;
235					}
236				}
237				$position++;
238			}
239			if ($found) {
240				return $position;
241			}
242			return false;
243		}
244		return strpos($haystack, $needle, $offset);
245	}
246
247/**
248 * Finds the last occurrence of a character in a string within another.
249 *
250 * @param string $haystack The string from which to get the last occurrence of $needle.
251 * @param string $needle The string to find in $haystack.
252 * @param bool $part Determines which portion of $haystack this function returns.
253 *    If set to true, it returns all of $haystack from the beginning to the last occurrence of $needle.
254 *    If set to false, it returns all of $haystack from the last occurrence of $needle to the end,
255 *    Default value is false.
256 * @return string|bool The portion of $haystack. or false if $needle is not found.
257 */
258	public static function strrchr($haystack, $needle, $part = false) {
259		$check = Multibyte::utf8($haystack);
260		$found = false;
261
262		$haystack = Multibyte::utf8($haystack);
263		$haystackCount = count($haystack);
264
265		$matches = array_count_values($check);
266
267		$needle = Multibyte::utf8($needle);
268		$needleCount = count($needle);
269
270		$parts = array();
271		$position = 0;
272
273		while (($found === false) && ($position < $haystackCount)) {
274			if (isset($needle[0]) && $needle[0] === $check[$position]) {
275				for ($i = 1; $i < $needleCount; $i++) {
276					if ($needle[$i] !== $check[$position + $i]) {
277						if ($needle[$i] === $check[($position + $i) - 1]) {
278							$found = true;
279						}
280						unset($parts[$position - 1]);
281						$haystack = array_merge(array($haystack[$position]), $haystack);
282						break;
283					}
284				}
285				if (isset($matches[$needle[0]]) && $matches[$needle[0]] > 1) {
286					$matches[$needle[0]] = $matches[$needle[0]] - 1;
287				} elseif ($i === $needleCount) {
288					$found = true;
289				}
290			}
291
292			if (!$found && isset($haystack[$position])) {
293				$parts[] = $haystack[$position];
294				unset($haystack[$position]);
295			}
296			$position++;
297		}
298
299		if ($found && $part && !empty($parts)) {
300			return Multibyte::ascii($parts);
301		} elseif ($found && !empty($haystack)) {
302			return Multibyte::ascii($haystack);
303		}
304		return false;
305	}
306
307/**
308 * Finds the last occurrence of a character in a string within another, case insensitive.
309 *
310 * @param string $haystack The string from which to get the last occurrence of $needle.
311 * @param string $needle The string to find in $haystack.
312 * @param bool $part Determines which portion of $haystack this function returns.
313 *    If set to true, it returns all of $haystack from the beginning to the last occurrence of $needle.
314 *    If set to false, it returns all of $haystack from the last occurrence of $needle to the end,
315 *    Default value is false.
316 * @return string|bool The portion of $haystack. or false if $needle is not found.
317 */
318	public static function strrichr($haystack, $needle, $part = false) {
319		$check = Multibyte::strtoupper($haystack);
320		$check = Multibyte::utf8($check);
321		$found = false;
322
323		$haystack = Multibyte::utf8($haystack);
324		$haystackCount = count($haystack);
325
326		$matches = array_count_values($check);
327
328		$needle = Multibyte::strtoupper($needle);
329		$needle = Multibyte::utf8($needle);
330		$needleCount = count($needle);
331
332		$parts = array();
333		$position = 0;
334
335		while (($found === false) && ($position < $haystackCount)) {
336			if (isset($needle[0]) && $needle[0] === $check[$position]) {
337				for ($i = 1; $i < $needleCount; $i++) {
338					if ($needle[$i] !== $check[$position + $i]) {
339						if ($needle[$i] === $check[($position + $i) - 1]) {
340							$found = true;
341						}
342						unset($parts[$position - 1]);
343						$haystack = array_merge(array($haystack[$position]), $haystack);
344						break;
345					}
346				}
347				if (isset($matches[$needle[0]]) && $matches[$needle[0]] > 1) {
348					$matches[$needle[0]] = $matches[$needle[0]] - 1;
349				} elseif ($i === $needleCount) {
350					$found = true;
351				}
352			}
353
354			if (!$found && isset($haystack[$position])) {
355				$parts[] = $haystack[$position];
356				unset($haystack[$position]);
357			}
358			$position++;
359		}
360
361		if ($found && $part && !empty($parts)) {
362			return Multibyte::ascii($parts);
363		} elseif ($found && !empty($haystack)) {
364			return Multibyte::ascii($haystack);
365		}
366		return false;
367	}
368
369/**
370 * Finds position of last occurrence of a string within another, case insensitive
371 *
372 * @param string $haystack The string from which to get the position of the last occurrence of $needle.
373 * @param string $needle The string to find in $haystack.
374 * @param int $offset The position in $haystack to start searching.
375 * @return int|bool The numeric position of the last occurrence of $needle in the $haystack string,
376 *    or false if $needle is not found.
377 */
378	public static function strripos($haystack, $needle, $offset = 0) {
379		if (Multibyte::checkMultibyte($haystack)) {
380			$found = false;
381			$haystack = Multibyte::strtoupper($haystack);
382			$haystack = Multibyte::utf8($haystack);
383			$haystackCount = count($haystack);
384
385			$matches = array_count_values($haystack);
386
387			$needle = Multibyte::strtoupper($needle);
388			$needle = Multibyte::utf8($needle);
389			$needleCount = count($needle);
390
391			$position = $offset;
392
393			while (($found === false) && ($position < $haystackCount)) {
394				if (isset($needle[0]) && $needle[0] === $haystack[$position]) {
395					for ($i = 1; $i < $needleCount; $i++) {
396						if ($needle[$i] !== $haystack[$position + $i]) {
397							if ($needle[$i] === $haystack[($position + $i) - 1]) {
398								$position--;
399								$found = true;
400								continue;
401							}
402						}
403					}
404
405					if (!$offset && isset($matches[$needle[0]]) && $matches[$needle[0]] > 1) {
406						$matches[$needle[0]] = $matches[$needle[0]] - 1;
407					} elseif ($i === $needleCount) {
408						$found = true;
409						$position--;
410					}
411				}
412				$position++;
413			}
414			return ($found) ? $position : false;
415		}
416		return strripos($haystack, $needle, $offset);
417	}
418
419/**
420 * Find position of last occurrence of a string in a string.
421 *
422 * @param string $haystack The string being checked, for the last occurrence of $needle.
423 * @param string $needle The string to find in $haystack.
424 * @param int $offset May be specified to begin searching an arbitrary number of characters into the string.
425 *    Negative values will stop searching at an arbitrary point prior to the end of the string.
426 * @return int|bool The numeric position of the last occurrence of $needle in the $haystack string.
427 *    If $needle is not found, it returns false.
428 */
429	public static function strrpos($haystack, $needle, $offset = 0) {
430		if (Multibyte::checkMultibyte($haystack)) {
431			$found = false;
432
433			$haystack = Multibyte::utf8($haystack);
434			$haystackCount = count($haystack);
435
436			$matches = array_count_values($haystack);
437
438			$needle = Multibyte::utf8($needle);
439			$needleCount = count($needle);
440
441			$position = $offset;
442
443			while (($found === false) && ($position < $haystackCount)) {
444				if (isset($needle[0]) && $needle[0] === $haystack[$position]) {
445					for ($i = 1; $i < $needleCount; $i++) {
446						if ($needle[$i] !== $haystack[$position + $i]) {
447							if ($needle[$i] === $haystack[($position + $i) - 1]) {
448								$position--;
449								$found = true;
450								continue;
451							}
452						}
453					}
454
455					if (!$offset && isset($matches[$needle[0]]) && $matches[$needle[0]] > 1) {
456						$matches[$needle[0]] = $matches[$needle[0]] - 1;
457					} elseif ($i === $needleCount) {
458						$found = true;
459						$position--;
460					}
461				}
462				$position++;
463			}
464			return ($found) ? $position : false;
465		}
466		return strrpos($haystack, $needle, $offset);
467	}
468
469/**
470 * Finds first occurrence of a string within another
471 *
472 * @param string $haystack The string from which to get the first occurrence of $needle.
473 * @param string $needle The string to find in $haystack
474 * @param bool $part Determines which portion of $haystack this function returns.
475 *    If set to true, it returns all of $haystack from the beginning to the first occurrence of $needle.
476 *    If set to false, it returns all of $haystack from the first occurrence of $needle to the end,
477 *    Default value is FALSE.
478 * @return string|bool The portion of $haystack, or true if $needle is not found.
479 */
480	public static function strstr($haystack, $needle, $part = false) {
481		$php = (PHP_VERSION < 5.3);
482
483		if (($php && $part) || Multibyte::checkMultibyte($haystack)) {
484			$check = Multibyte::utf8($haystack);
485			$found = false;
486
487			$haystack = Multibyte::utf8($haystack);
488			$haystackCount = count($haystack);
489
490			$needle = Multibyte::utf8($needle);
491			$needleCount = count($needle);
492
493			$parts = array();
494			$position = 0;
495
496			while (($found === false) && ($position < $haystackCount)) {
497				if (isset($needle[0]) && $needle[0] === $check[$position]) {
498					for ($i = 1; $i < $needleCount; $i++) {
499						if ($needle[$i] !== $check[$position + $i]) {
500							break;
501						}
502					}
503					if ($i === $needleCount) {
504						$found = true;
505					}
506				}
507				if (!$found) {
508					$parts[] = $haystack[$position];
509					unset($haystack[$position]);
510				}
511				$position++;
512			}
513
514			if ($found && $part && !empty($parts)) {
515				return Multibyte::ascii($parts);
516			} elseif ($found && !empty($haystack)) {
517				return Multibyte::ascii($haystack);
518			}
519			return false;
520		}
521
522		if (!$php) {
523			return strstr($haystack, $needle, $part);
524		}
525		return strstr($haystack, $needle);
526	}
527
528/**
529 * Make a string lowercase
530 *
531 * @param string $string The string being lowercased.
532 * @return string with all alphabetic characters converted to lowercase.
533 */
534	public static function strtolower($string) {
535		$utf8Map = Multibyte::utf8($string);
536
537		$length = count($utf8Map);
538		$lowerCase = array();
539
540		for ($i = 0; $i < $length; $i++) {
541			$char = $utf8Map[$i];
542
543			if ($char < 128) {
544				$str = strtolower(chr($char));
545				$strlen = strlen($str);
546				for ($ii = 0; $ii < $strlen; $ii++) {
547					$lower = ord(substr($str, $ii, 1));
548				}
549				$lowerCase[] = $lower;
550				$matched = true;
551			} else {
552				$matched = false;
553				$keys = static::_find($char, 'upper');
554
555				if (!empty($keys)) {
556					foreach ($keys as $key => $value) {
557						if ($keys[$key]['upper'] == $char && count($keys[$key]['lower']) > 0) {
558							$lowerCase[] = $keys[$key]['lower'][0];
559							$matched = true;
560							break 1;
561						}
562					}
563				}
564			}
565			if ($matched === false) {
566				$lowerCase[] = $char;
567			}
568		}
569		return Multibyte::ascii($lowerCase);
570	}
571
572/**
573 * Make a string uppercase
574 *
575 * @param string $string The string being uppercased.
576 * @return string with all alphabetic characters converted to uppercase.
577 */
578	public static function strtoupper($string) {
579		$utf8Map = Multibyte::utf8($string);
580
581		$length = count($utf8Map);
582		$replaced = array();
583		$upperCase = array();
584
585		for ($i = 0; $i < $length; $i++) {
586			$char = $utf8Map[$i];
587
588			if ($char < 128) {
589				$str = strtoupper(chr($char));
590				$strlen = strlen($str);
591				for ($ii = 0; $ii < $strlen; $ii++) {
592					$upper = ord(substr($str, $ii, 1));
593				}
594				$upperCase[] = $upper;
595				$matched = true;
596
597			} else {
598				$matched = false;
599				$keys = static::_find($char);
600				$keyCount = count($keys);
601
602				if (!empty($keys)) {
603					foreach ($keys as $key => $value) {
604						$matched = false;
605						$replace = 0;
606						if ($length > 1 && count($keys[$key]['lower']) > 1) {
607							$j = 0;
608
609							for ($ii = 0, $count = count($keys[$key]['lower']); $ii < $count; $ii++) {
610								$nextChar = $utf8Map[$i + $ii];
611
612								if (isset($nextChar) && ($nextChar == $keys[$key]['lower'][$j + $ii])) {
613									$replace++;
614								}
615							}
616							if ($replace == $count) {
617								$upperCase[] = $keys[$key]['upper'];
618								$replaced = array_merge($replaced, array_values($keys[$key]['lower']));
619								$matched = true;
620								break 1;
621							}
622						} elseif ($length > 1 && $keyCount > 1) {
623							$j = 0;
624							for ($ii = 1; $ii < $keyCount; $ii++) {
625								$nextChar = $utf8Map[$i + $ii - 1];
626
627								if (in_array($nextChar, $keys[$ii]['lower'])) {
628
629									for ($jj = 0, $count = count($keys[$ii]['lower']); $jj < $count; $jj++) {
630										$nextChar = $utf8Map[$i + $jj];
631
632										if (isset($nextChar) && ($nextChar == $keys[$ii]['lower'][$j + $jj])) {
633											$replace++;
634										}
635									}
636									if ($replace == $count) {
637										$upperCase[] = $keys[$ii]['upper'];
638										$replaced = array_merge($replaced, array_values($keys[$ii]['lower']));
639										$matched = true;
640										break 2;
641									}
642								}
643							}
644						}
645						if ($keys[$key]['lower'][0] == $char) {
646							$upperCase[] = $keys[$key]['upper'];
647							$matched = true;
648							break 1;
649						}
650					}
651				}
652			}
653			if ($matched === false && !in_array($char, $replaced, true)) {
654				$upperCase[] = $char;
655			}
656		}
657		return Multibyte::ascii($upperCase);
658	}
659
660/**
661 * Count the number of substring occurrences
662 *
663 * @param string $haystack The string being checked.
664 * @param string $needle The string being found.
665 * @return int The number of times the $needle substring occurs in the $haystack string.
666 */
667	public static function substrCount($haystack, $needle) {
668		$count = 0;
669		$haystack = Multibyte::utf8($haystack);
670		$haystackCount = count($haystack);
671		$matches = array_count_values($haystack);
672		$needle = Multibyte::utf8($needle);
673		$needleCount = count($needle);
674
675		if ($needleCount === 1 && isset($matches[$needle[0]])) {
676			return $matches[$needle[0]];
677		}
678
679		for ($i = 0; $i < $haystackCount; $i++) {
680			if (isset($needle[0]) && $needle[0] === $haystack[$i]) {
681				for ($ii = 1; $ii < $needleCount; $ii++) {
682					if ($needle[$ii] === $haystack[$i + 1]) {
683						if ((isset($needle[$ii + 1]) && $haystack[$i + 2]) && $needle[$ii + 1] !== $haystack[$i + 2]) {
684							$count--;
685						} else {
686							$count++;
687						}
688					}
689				}
690			}
691		}
692		return $count;
693	}
694
695/**
696 * Get part of string
697 *
698 * @param string $string The string being checked.
699 * @param int $start The first position used in $string.
700 * @param int $length The maximum length of the returned string.
701 * @return string The portion of $string specified by the $string and $length parameters.
702 */
703	public static function substr($string, $start, $length = null) {
704		if ($start === 0 && $length === null) {
705			return $string;
706		}
707
708		$string = Multibyte::utf8($string);
709
710		for ($i = 1; $i <= $start; $i++) {
711			unset($string[$i - 1]);
712		}
713
714		if ($length === null || count($string) < $length) {
715			return Multibyte::ascii($string);
716		}
717		$string = array_values($string);
718
719		$value = array();
720		for ($i = 0; $i < $length; $i++) {
721			$value[] = $string[$i];
722		}
723		return Multibyte::ascii($value);
724	}
725
726/**
727 * Prepare a string for mail transport, using the provided encoding
728 *
729 * @param string $string value to encode
730 * @param string $charset charset to use for encoding. defaults to UTF-8
731 * @param string $newline Newline string.
732 * @return string
733 */
734	public static function mimeEncode($string, $charset = null, $newline = "\r\n") {
735		if (!Multibyte::checkMultibyte($string) && strlen($string) < 75) {
736			return $string;
737		}
738
739		if (empty($charset)) {
740			$charset = Configure::read('App.encoding');
741		}
742		$charset = strtoupper($charset);
743
744		$start = '=?' . $charset . '?B?';
745		$end = '?=';
746		$spacer = $end . $newline . ' ' . $start;
747
748		$length = 75 - strlen($start) - strlen($end);
749		$length = $length - ($length % 4);
750		if ($charset === 'UTF-8') {
751			$parts = array();
752			$maxchars = floor(($length * 3) / 4);
753			$stringLength = strlen($string);
754			while ($stringLength > $maxchars) {
755				$i = (int)$maxchars;
756				$test = ord($string[$i]);
757				while ($test >= 128 && $test <= 191) {
758					$i--;
759					$test = ord($string[$i]);
760				}
761				$parts[] = base64_encode(substr($string, 0, $i));
762				$string = substr($string, $i);
763				$stringLength = strlen($string);
764			}
765			$parts[] = base64_encode($string);
766			$string = implode($spacer, $parts);
767		} else {
768			$string = chunk_split(base64_encode($string), $length, $spacer);
769			$string = preg_replace('/' . preg_quote($spacer) . '$/', '', $string);
770		}
771		return $start . $string . $end;
772	}
773
774/**
775 * Return the Code points range for Unicode characters
776 *
777 * @param int $decimal Decimal value.
778 * @return string
779 */
780	protected static function _codepoint($decimal) {
781		if ($decimal > 128 && $decimal < 256) {
782			$return = '0080_00ff'; // Latin-1 Supplement
783		} elseif ($decimal < 384) {
784			$return = '0100_017f'; // Latin Extended-A
785		} elseif ($decimal < 592) {
786			$return = '0180_024F'; // Latin Extended-B
787		} elseif ($decimal < 688) {
788			$return = '0250_02af'; // IPA Extensions
789		} elseif ($decimal >= 880 && $decimal < 1024) {
790			$return = '0370_03ff'; // Greek and Coptic
791		} elseif ($decimal < 1280) {
792			$return = '0400_04ff'; // Cyrillic
793		} elseif ($decimal < 1328) {
794			$return = '0500_052f'; // Cyrillic Supplement
795		} elseif ($decimal < 1424) {
796			$return = '0530_058f'; // Armenian
797		} elseif ($decimal >= 7680 && $decimal < 7936) {
798			$return = '1e00_1eff'; // Latin Extended Additional
799		} elseif ($decimal < 8192) {
800			$return = '1f00_1fff'; // Greek Extended
801		} elseif ($decimal >= 8448 && $decimal < 8528) {
802			$return = '2100_214f'; // Letterlike Symbols
803		} elseif ($decimal < 8592) {
804			$return = '2150_218f'; // Number Forms
805		} elseif ($decimal >= 9312 && $decimal < 9472) {
806			$return = '2460_24ff'; // Enclosed Alphanumerics
807		} elseif ($decimal >= 11264 && $decimal < 11360) {
808			$return = '2c00_2c5f'; // Glagolitic
809		} elseif ($decimal < 11392) {
810			$return = '2c60_2c7f'; // Latin Extended-C
811		} elseif ($decimal < 11520) {
812			$return = '2c80_2cff'; // Coptic
813		} elseif ($decimal >= 65280 && $decimal < 65520) {
814			$return = 'ff00_ffef'; // Halfwidth and Fullwidth Forms
815		} else {
816			$return = false;
817		}
818		static::$_codeRange[$decimal] = $return;
819		return $return;
820	}
821
822/**
823 * Find the related code folding values for $char
824 *
825 * @param int $char decimal value of character
826 * @param string $type Type 'lower' or 'upper'. Defaults to 'lower'.
827 * @return array
828 */
829	protected static function _find($char, $type = 'lower') {
830		$found = array();
831		if (!isset(static::$_codeRange[$char])) {
832			$range = static::_codepoint($char);
833			if ($range === false) {
834				return array();
835			}
836			if (!Configure::configured('_cake_core_')) {
837				App::uses('PhpReader', 'Configure');
838				Configure::config('_cake_core_', new PhpReader(CAKE . 'Config' . DS));
839			}
840			Configure::load('unicode' . DS . 'casefolding' . DS . $range, '_cake_core_');
841			static::$_caseFold[$range] = Configure::read($range);
842			Configure::delete($range);
843		}
844
845		if (!static::$_codeRange[$char]) {
846			return array();
847		}
848		static::$_table = static::$_codeRange[$char];
849		$count = count(static::$_caseFold[static::$_table]);
850
851		for ($i = 0; $i < $count; $i++) {
852			if ($type === 'lower' && static::$_caseFold[static::$_table][$i][$type][0] === $char) {
853				$found[] = static::$_caseFold[static::$_table][$i];
854			} elseif ($type === 'upper' && static::$_caseFold[static::$_table][$i][$type] === $char) {
855				$found[] = static::$_caseFold[static::$_table][$i];
856			}
857		}
858		return $found;
859	}
860
861/**
862 * Check the $string for multibyte characters
863 *
864 * @param string $string Value to test.
865 * @return bool
866 */
867	public static function checkMultibyte($string) {
868		$length = strlen($string);
869
870		for ($i = 0; $i < $length; $i++) {
871			$value = ord(($string[$i]));
872			if ($value > 128) {
873				return true;
874			}
875		}
876		return false;
877	}
878
879}
880