+/**
+ * Removes non-unicode characters from input
+ *
+ * @param mixed $input String or array.
+ * @return string
+ */
+function rc_utf8_clean($input)
+{
+ // handle input of type array
+ if (is_array($input)) {
+ foreach ($input as $idx => $val)
+ $input[$idx] = rc_utf8_clean($val);
+ return $input;
+ }
+
+ if (!is_string($input) || $input == '')
+ return $input;
+
+ // iconv/mbstring are much faster (especially with long strings)
+ if (function_exists('mb_convert_encoding') && ($res = mb_convert_encoding($input, 'UTF-8', 'UTF-8')) !== false)
+ return $res;
+
+ if (function_exists('iconv') && ($res = @iconv('UTF-8', 'UTF-8//IGNORE', $input)) !== false)
+ return $res;
+
+ $regexp = '/^('.
+// '[\x00-\x7F]'. // UTF8-1
+ '|[\xC2-\xDF][\x80-\xBF]'. // UTF8-2
+ '|\xE0[\xA0-\xBF][\x80-\xBF]'. // UTF8-3
+ '|[\xE1-\xEC][\x80-\xBF][\x80-\xBF]'. // UTF8-3
+ '|\xED[\x80-\x9F][\x80-\xBF]'. // UTF8-3
+ '|[\xEE-\xEF][\x80-\xBF][\x80-\xBF]'. // UTF8-3
+ '|\xF0[\x90-\xBF][\x80-\xBF][\x80-\xBF]'. // UTF8-4
+ '|[\xF1-\xF3][\x80-\xBF][\x80-\xBF][\x80-\xBF]'.// UTF8-4
+ '|\xF4[\x80-\x8F][\x80-\xBF][\x80-\xBF]'. // UTF8-4
+ ')$/';
+
+ $seq = '';
+ $out = '';
+
+ for ($i = 0, $len = strlen($input); $i < $len; $i++) {
+ $chr = $input[$i];
+ $ord = ord($chr);
+ // 1-byte character
+ if ($ord <= 0x7F) {
+ if ($seq)
+ $out .= preg_match($regexp, $seq) ? $seq : '';
+ $seq = '';
+ $out .= $chr;
+ // first (or second) byte of multibyte sequence
+ } else if ($ord >= 0xC0) {
+ if (strlen($seq)>1) {
+ $out .= preg_match($regexp, $seq) ? $seq : '';
+ $seq = '';
+ } else if ($seq && ord($seq) < 0xC0) {
+ $seq = '';
+ }
+ $seq .= $chr;
+ // next byte of multibyte sequence
+ } else if ($seq) {
+ $seq .= $chr;
+ }
+ }
+
+ if ($seq)
+ $out .= preg_match($regexp, $seq) ? $seq : '';
+
+ return $out;
+}
+
+
+/**
+ * Convert a variable into a javascript object notation
+ *
+ * @param mixed Input value
+ * @return string Serialized JSON string
+ */
+function json_serialize($input)
+{
+ $input = rc_utf8_clean($input);
+
+ // sometimes even using rc_utf8_clean() the input contains invalid UTF-8 sequences
+ // that's why we have @ here
+ return @json_encode($input);
+}
+
+
+/**
+ * Explode quoted string
+ *
+ * @param string Delimiter expression string for preg_match()
+ * @param string Input string
+ */
+function rcube_explode_quoted_string($delimiter, $string)
+{
+ $result = array();
+ $strlen = strlen($string);
+
+ for ($q=$p=$i=0; $i < $strlen; $i++) {
+ if ($string[$i] == "\"" && $string[$i-1] != "\\") {
+ $q = $q ? false : true;
+ }
+ else if (!$q && preg_match("/$delimiter/", $string[$i])) {
+ $result[] = substr($string, $p, $i - $p);
+ $p = $i + 1;
+ }
+ }
+
+ $result[] = substr($string, $p);
+ return $result;
+}
+
+
+/**
+ * Get all keys from array (recursive)
+ *
+ * @param array Input array
+ * @return array
+ */
+function array_keys_recursive($array)
+{
+ $keys = array();
+
+ if (!empty($array))
+ foreach ($array as $key => $child) {
+ $keys[] = $key;
+ foreach (array_keys_recursive($child) as $val)
+ $keys[] = $val;
+ }
+ return $keys;
+}
+
+
+/**
+ * mbstring replacement functions
+ */
+
+if (!extension_loaded('mbstring'))
+{
+ function mb_strlen($str)
+ {
+ return strlen($str);
+ }
+
+ function mb_strtolower($str)
+ {
+ return strtolower($str);
+ }
+
+ function mb_strtoupper($str)
+ {
+ return strtoupper($str);
+ }
+
+ function mb_substr($str, $start, $len=null)
+ {
+ return substr($str, $start, $len);
+ }
+
+ function mb_strpos($haystack, $needle, $offset=0)
+ {
+ return strpos($haystack, $needle, $offset);
+ }
+
+ function mb_strrpos($haystack, $needle, $offset=0)
+ {
+ return strrpos($haystack, $needle, $offset);
+ }
+}
+
+/**
+ * intl replacement functions
+ */
+
+if (!function_exists('idn_to_utf8'))
+{
+ function idn_to_utf8($domain, $flags=null)
+ {
+ static $idn, $loaded;
+
+ if (!$loaded) {
+ $idn = new Net_IDNA2();
+ $loaded = true;
+ }
+
+ if ($idn && $domain && preg_match('/(^|\.)xn--/i', $domain)) {
+ try {
+ $domain = $idn->decode($domain);
+ }
+ catch (Exception $e) {
+ }
+ }
+ return $domain;
+ }
+}
+
+if (!function_exists('idn_to_ascii'))
+{
+ function idn_to_ascii($domain, $flags=null)
+ {
+ static $idn, $loaded;
+
+ if (!$loaded) {
+ $idn = new Net_IDNA2();
+ $loaded = true;
+ }
+
+ if ($idn && $domain && preg_match('/[^\x20-\x7E]/', $domain)) {
+ try {
+ $domain = $idn->encode($domain);
+ }
+ catch (Exception $e) {
+ }
+ }
+ return $domain;
+ }
+}
+