'ok' - Success 1 => $preferred_lang 0 => 'nomatch' - No match, use default 1 => $lang_list_org[0] 0 => 'none' - No match, all unacceptable 0 => 'error' - Failure (bad input etc.) 1 => $reason ---------- $lang_accept: "da, en-gb;q=0.82, en;q=0.7, fr, de;q=0.009, zh;q=0" $lang_list: array("en", "fr") selects: "fr" Ranking Algorithm: Lang : quality padding spec_bonus position 0 = Weight (Rank) da : 1 000 0 4 0 = 1000040 (1) en-gb : 0.82 0 1 3 0 = 820130 (3) en : 0.7 00 0 2 0 = 700020 (4) fr : 1 000 0 1 0 = 1000010 (2) de : 0.009 0 0 0 = 9000 (5) zh : 0 - - - - = - (-) To Do: Clean-up Validation of input Testcases */ $error = FALSE; $lang_list_org = $lang_list; // Make a copy for future use $lang_accept = array_reverse(array_map('trim',explode(',', $lang_accept))); foreach ($lang_accept as $key => $value) { $value = explode(';q=', $value); // If a quality value hasn't been set explicitly it defaults to 1 if (!isset($value[1]) && $value[0] != '*') { $value[1] = 1; } if ($value[0] == '*') { $catchall = TRUE; $value[1] = 0; // To avoid error messages } // Pad $value[1] (q) if ($value[1] == 1) { $value[1] = 1000; } else { $value[1] = str_replace('0.', '', $value[1]); // Make fractions (0.8) into integers (8) $value[1] = str_pad($value[1], 3, 0); } // 'en-gb' is more specific than 'en', so give more weight strpos($value[0], '-') !== FALSE ? $spec_bonus = 1 : $spec_bonus = 0; // Assemble the rank $rank = $value[1].$spec_bonus.$key. 0; // The 0 is to make sure that there's always a difference of 10 between each lang // Separate acceptable from unacceptable languages if ($value[1] != 0 && $value[0] != '*') { // * is a "last resort" selector that is only needed if no match is found, so no reason to have it in the list $lang_ranks[$value[0]] = (int)$rank; } elseif ($value[1] == 0 && $value[0] != '*') { $unacceptable[] = $value[0]; } } unset($key); unset($value); // Avoids trouble if (!isset($unacceptable)) { $unacceptable = array(); } // We do a sort now so that we get the correct (lowest) ranks in the fallback list arsort($lang_ranks); // Create a fallback list of language codes such as 'en' by reducing compound codes ('en-gb') to just the language part ('en') // Used when there is no general fallback in the main list ('en-gb, fr' -> 'en-gb, en, fr') foreach ($lang_ranks as $key => $value) { if (strpos($key, '-') !== FALSE) { $fallback[substr($key, 0, 2)] = $value - 1; // We subtract 1 so that it will be lower in the list } } unset($key); unset($value); // Avoids trouble if (!isset($fallback)) { $fallback = array(); } // Add the fallback list to the main one. array_merge() seems to be broken (php 5.0.4) (or I'm a dork), so we do this instead foreach ($fallback as $key => $value) { if (!array_key_exists($key, $lang_ranks)) { $lang_ranks[$key] = $value; } } unset($key); unset($value); // Remove any unacceptable languages from $lang_list $lang_list = array_diff($lang_list, $unacceptable); arsort($lang_ranks); $lang_ranks = array_flip($lang_ranks); // Keep only the languages that we can actually use $lang_ranks = array_intersect($lang_ranks, $lang_list); $preferred_lang = reset($lang_ranks); // An acceptable match has been found if (($preferred_lang != FALSE) && (count($lang_list) != 0) && ($error == FALSE)) { $status = 'ok'; } // No match was found, so we simply return the first in $lang_list elseif (($preferred_lang == FALSE) && (count($lang_list) != 0) && ($error == FALSE)) { $preferred_lang = reset($lang_list); $status = 'nomatch'; } // No match and $lang_list contains no acceptable languges elseif (($preferred_lang == FALSE) && (count($lang_list) == 0) && ($error == FALSE)) { $status = 'none'; } if ($status == 'ok') { $foo = array(0 => 'ok', 1 => $preferred_lang); } if ($status == 'nomatch') { $foo = array(0 => 'nomatch', 1 => $preferred_lang); } if ($status == 'none') { $foo = array(0 => 'none'); } if ($error != FALSE) { $foo = array(0 => 'error', 1 => $error_reason); } return $foo; } ?>