language.inc (mrbs-1.9.4) | : | language.inc (mrbs-1.10.0) | ||
---|---|---|---|---|
<?php | <?php | |||
namespace MRBS; | namespace MRBS; | |||
// Note that we are using \MRBS\Locale because \Locale has some bugs in it (and also | // Note that we are using \MRBS\Locale because \Locale has some bugs in it (and also | |||
// isn't always installed). | // isn't always installed). | |||
use IntlDateFormatter; | ||||
use PHPMailer\PHPMailer\PHPMailer; | ||||
require_once "functions.inc"; | require_once "functions.inc"; | |||
// A map of browser locale aliases | // A map of browser locale aliases | |||
$lang_aliases = array | $lang_aliases = array | |||
( | ( | |||
'no' => 'nb', // Not all operating systems will accept a locale of 'no ' | 'no' => 'nb', // Not all operating systems will accept a locale of 'no ' | |||
'sh' => 'sr-latn-rs', | 'sh' => 'sr-latn-rs', | |||
); | ); | |||
// Map non-standard language codes used by flatpickr onto BCP 47 language tags | // Map non-standard language codes used by flatpickr onto BCP 47 language tags | |||
skipping to change at line 163 | skipping to change at line 166 | |||
// that best fits the config settings and the user's browser preferences. Then it | // that best fits the config settings and the user's browser preferences. Then it | |||
// finds a language that best fits the locale, subject to any overriding config settings. | // finds a language that best fits the locale, subject to any overriding config settings. | |||
// | // | |||
// Locales will also be needed for third party JavaScript libraries such as date pickers. | // Locales will also be needed for third party JavaScript libraries such as date pickers. | |||
// MRBS tries to find the best match for these depending on the localisations av ailable | // MRBS tries to find the best match for these depending on the localisations av ailable | |||
// for the library. This may sometimes mean that the locale for the library, e g the | // for the library. This may sometimes mean that the locale for the library, e g the | |||
// datepicker, is different from the main locale. But this is probably better than | // datepicker, is different from the main locale. But this is probably better than | |||
// trying to find a locale that is available on the server and has a translation available | // trying to find a locale that is available on the server and has a translation available | |||
// in MRBS and has localisations available in all the third party libraries. | // in MRBS and has localisations available in all the third party libraries. | |||
language_debug('HTTP_ACCEPT_LANGUAGE: ' . $server['HTTP_ACCEPT_LANGUAGE']); | $http_accept_language = $server['HTTP_ACCEPT_LANGUAGE'] ?? ''; | |||
language_debug("HTTP_ACCEPT_LANGUAGE: $http_accept_language"); | ||||
// Translate languages in the header string to their aliases | // Translate languages in the header string to their aliases | |||
if (isset($server['HTTP_ACCEPT_LANGUAGE'])) | if (isset($server['HTTP_ACCEPT_LANGUAGE'])) | |||
{ | { | |||
$aliased_header = alias_header($server['HTTP_ACCEPT_LANGUAGE'], $lang_aliases) ; | $aliased_header = alias_header($server['HTTP_ACCEPT_LANGUAGE'], $lang_aliases) ; | |||
} | } | |||
else | else | |||
{ | { | |||
$aliased_header = null; | $aliased_header = null; | |||
} | } | |||
language_debug('$aliased_header: ' . $aliased_header); | language_debug('$aliased_header: ' . $aliased_header); | |||
skipping to change at line 298 | skipping to change at line 303 | |||
$locale = Locale::acceptFromHttp($header); | $locale = Locale::acceptFromHttp($header); | |||
} | } | |||
// If there aren't any browser preferences, use English | // If there aren't any browser preferences, use English | |||
else | else | |||
{ | { | |||
$locale = 'en'; | $locale = 'en'; | |||
} | } | |||
// Convert it to BCP 47 format (eg convert 'en_US' to 'en-US') | // Convert it to BCP 47 format (eg convert 'en_US' to 'en-US') | |||
$locale = convert_to_BCP47($locale); | $locale = convert_to_BCP47($locale); | |||
$have_locale = true; | ||||
} | } | |||
return $locale; | return $locale; | |||
} | } | |||
// Returns a simple array of BCP 47 language tags in decreasing order of prefere nce. This | // Returns a simple array of BCP 47 language tags in decreasing order of prefere nce. This | |||
// is designed for use with third party libraries (eg datepickers, datatables) w here the | // is designed for use with third party libraries (eg datepickers, datatables) w here the | |||
// locale used by MRBS, and given by get_mrbs_locale(), may not be available. S o this function | // locale used by MRBS, and given by get_mrbs_locale(), may not be available. S o this function | |||
// returns as wide a list of languages as possible, in the hope that one of them may be | // returns as wide a list of languages as possible, in the hope that one of them may be | |||
// supported by the third party library. | // supported by the third party library. | |||
skipping to change at line 395 | skipping to change at line 401 | |||
$available_languages = get_langtags(MRBS_ROOT . '/lang', 'lang.'); | $available_languages = get_langtags(MRBS_ROOT . '/lang', 'lang.'); | |||
$lang = Locale::lookup($available_languages, $locale, false, $default_lang uage_tokens); | $lang = Locale::lookup($available_languages, $locale, false, $default_lang uage_tokens); | |||
} | } | |||
} | } | |||
return $lang; | return $lang; | |||
} | } | |||
// Returns a version of the Accept-Language request HTTP header with language | // Returns a version of the Accept-Language request HTTP header with language | |||
// strings substituted for their aliases | // strings substituted for their aliases | |||
function alias_header($header, array $aliases) | function alias_header(string $header, array $aliases) : string | |||
{ | { | |||
if (!empty($aliases)) | if (!empty($aliases)) | |||
{ | { | |||
$patterns = array(); | $patterns = array(); | |||
$replacements = array(); | $replacements = array(); | |||
foreach ($aliases as $key => $value) | foreach ($aliases as $key => $value) | |||
{ | { | |||
$patterns[] = "/(?<=^|,)($key)(?=,|;|$)/i"; | $patterns[] = "/(?<=^|,)($key)(?=,|;|$)/i"; | |||
$replacements[] = $value; | $replacements[] = $value; | |||
skipping to change at line 418 | skipping to change at line 424 | |||
$header = preg_replace($patterns, $replacements, $header); | $header = preg_replace($patterns, $replacements, $header); | |||
} | } | |||
return $header; | return $header; | |||
} | } | |||
// Returns a sorted associative array of acceptable language qualifiers, indexed | // Returns a sorted associative array of acceptable language qualifiers, indexed | |||
// by language, given an Accept-Language header string. | // by language, given an Accept-Language header string. | |||
// If $translate_wildcard is set then the wildcard language identifier ('*') is | // If $translate_wildcard is set then the wildcard language identifier ('*') is | |||
// translated to a standard language - we use 'en'. | // translated to a standard language - we use 'en'. | |||
function get_qualifiers($header, $translate_wildcard=false) | function get_qualifiers(?string $header, bool $translate_wildcard=false) : array | |||
{ | { | |||
$result = array(); | $result = array(); | |||
if (!empty($header)) | if (!empty($header)) | |||
{ | { | |||
$lang_specifiers = explode(',', $header); | $lang_specifiers = explode(',', $header); | |||
foreach ($lang_specifiers as $specifier) | foreach ($lang_specifiers as $specifier) | |||
{ | { | |||
unset($weight); | unset($weight); | |||
skipping to change at line 474 | skipping to change at line 480 | |||
} | } | |||
arsort($result, SORT_NUMERIC); | arsort($result, SORT_NUMERIC); | |||
return $result; | return $result; | |||
} | } | |||
// Returns a string of acceptable languages, sorted in decreasing order of prefe rence | // Returns a string of acceptable languages, sorted in decreasing order of prefe rence | |||
// If $translate_wildcard is set then the wildcard language identifier ('*') is | // If $translate_wildcard is set then the wildcard language identifier ('*') is | |||
// translated to a standard language - we use 'en'. | // translated to a standard language - we use 'en'. | |||
function get_browser_langs($header, $translate_wildcard=false) | function get_browser_langs(?string $header, bool $translate_wildcard=false) : ar ray | |||
{ | { | |||
return array_keys(get_qualifiers($header, $translate_wildcard)); | return array_keys(get_qualifiers($header, $translate_wildcard)); | |||
} | } | |||
// Gets all the language tags in a directory where the filenames are of the form at | // Gets all the language tags in a directory where the filenames are of the form at | |||
// $prefix . $lang . $suffix. Returns an array. | // $prefix . $lang . $suffix. Returns an array. | |||
function get_langtags($dir, $prefix='', $suffix='') | function get_langtags($dir, $prefix='', $suffix='') | |||
{ | { | |||
$result = array(); | $result = array(); | |||
skipping to change at line 715 | skipping to change at line 721 | |||
} | } | |||
} | } | |||
function get_charset() | function get_charset() | |||
{ | { | |||
return 'utf-8'; | return 'utf-8'; | |||
} | } | |||
function get_mail_charset() | function get_mail_charset() | |||
{ | { | |||
return 'utf-8'; | return PHPMailer::CHARSET_UTF8; | |||
} | } | |||
function get_csv_charset() | function get_csv_charset() | |||
{ | { | |||
global $csv_charset; | global $csv_charset; | |||
if (empty($csv_charset)) | if (empty($csv_charset)) | |||
{ | { | |||
return get_charset(); | return get_charset(); | |||
} | } | |||
else | else | |||
skipping to change at line 761 | skipping to change at line 767 | |||
break; | break; | |||
} | } | |||
} | } | |||
// Get a vocab item, in UTF-8 | // Get a vocab item, in UTF-8 | |||
// Takes additional parameters as for sprintf() | // Takes additional parameters as for sprintf() | |||
// | // | |||
// [Maybe in the future we should switch to using the MessageFormatter | // [Maybe in the future we should switch to using the MessageFormatter | |||
// class as it is more powerful. However the Intl extension isn't present | // class as it is more powerful. However the Intl extension isn't present | |||
// in all PHP installations and so the class would have to be emulated] | // in all PHP installations and so the class would have to be emulated] | |||
function get_vocab($tag) | function get_vocab(string $tag) : string | |||
{ | { | |||
global $vocab; | global $vocab; | |||
// Return the tag itself if we can't find a vocab string | // Return the tag itself if we can't find a vocab string | |||
if (!isset($vocab[$tag])) | if (!isset($vocab[$tag])) | |||
{ | { | |||
return $tag; | return $tag; | |||
} | } | |||
$args = func_get_args(); | $args = func_get_args(); | |||
$args[0] = $vocab[$tag]; | $args[0] = $vocab[$tag]; | |||
return call_user_func_array('sprintf', $args); | return call_user_func_array('sprintf', $args); | |||
} | } | |||
// Same as get_vocab(), but uses the mailing language | // Same as get_vocab(), but uses the mailing language | |||
function get_mail_vocab($tag) | function get_mail_vocab(string $tag) : string | |||
{ | { | |||
global $vocab, $mail_settings; | global $vocab, $mail_settings; | |||
static $mail_vocab = null; | static $mail_vocab = null; | |||
if (!isset($mail_vocab)) | if (!isset($mail_vocab)) | |||
{ | { | |||
$web_vocab = $vocab; // Save $vocab before it gets overwritten | $web_vocab = $vocab; // Save $vocab before it gets overwritten | |||
set_vocab($mail_settings['admin_lang']); | set_vocab($mail_settings['admin_lang']); | |||
$mail_vocab = $vocab; | $mail_vocab = $vocab; | |||
skipping to change at line 806 | skipping to change at line 812 | |||
} | } | |||
$args = func_get_args(); | $args = func_get_args(); | |||
$args[0] = $mail_vocab[$tag]; | $args[0] = $mail_vocab[$tag]; | |||
$result = call_user_func_array('sprintf', $args); | $result = call_user_func_array('sprintf', $args); | |||
return str_replace(' ', ' ', $result); | return str_replace(' ', ' ', $result); | |||
} | } | |||
// Get localised booking type name | // Get localised booking type name | |||
function get_type_vocab($type) | function get_type_vocab(string $type) : string | |||
{ | { | |||
return get_vocab("type.$type"); | return get_vocab("type.$type"); | |||
} | } | |||
// Get localized field name for a user defined table column | // Get localized field name for a user defined table column | |||
// Looks for a tag of the format tablename.columnname (where tablename is | // Looks for a tag of the format tablename.columnname (where tablename is | |||
// stripped of the table prefix) and if can't find a string for that tag will | // stripped of the table prefix) and if can't find a string for that tag will | |||
// return the column name | // return the column name | |||
function get_loc_field_name($table, $name) | function get_loc_field_name(string $table, string $name) : string | |||
{ | { | |||
global $vocab; | global $vocab; | |||
// Get the qualified prefix | $tag = get_table_short_name($table) . ".$name"; | |||
$full_prefix = _tbl(''); | ||||
// Strip the prefix, if any, off the table name | ||||
if (utf8_strpos($table, $full_prefix) === 0) | ||||
{ | ||||
$tag = utf8_substr($table, utf8_strlen($full_prefix)); | ||||
} | ||||
else | ||||
{ | ||||
$tag = $table; | ||||
} | ||||
// Add on the field name | ||||
$tag .= "." . $name; | ||||
// Then if there's a string in the vocab array for $tag use that, | // If there's a string in the vocab array for $tag use that, | |||
// otherwise just use the fieldname | // otherwise just use the fieldname | |||
return (isset($vocab[$tag])) ? get_vocab($tag) : $name; | return (isset($vocab[$tag])) ? get_vocab($tag) : $name; | |||
} | } | |||
// optionally switch to a new locale, switching back at the end. | // Format a local time/date according to locale settings, returning the | |||
// $temp_locale can either be a string or an array of locales. | // result as a UTF-8 string. This function is based on strftime() | |||
function utf8_strftime($format, $time, $temp_locale=null) | // $time can be an int or a float (union type declarations not supported until P | |||
{ | HP 8.0) | |||
// $locale can either be a string or an array of locales. If $locale | ||||
// is not set then the current locale is used. | ||||
function date_formatter_strftime(string $format, $time, $locale) | ||||
{ | ||||
// Cast $time to an integer because we're going to use it in strftime() and da | ||||
te(), both | ||||
// of which expect integers and will otherwise throw an E_DEPRECATED error in | ||||
PHP 8.1 | ||||
$time = (int) $time; | ||||
$server_os = System::getServerOS(); | $server_os = System::getServerOS(); | |||
// Set the temporary locale. Note that $temp_locale could be an array of loca les, | // Set the temporary locale. Note that $temp_locale could be an array of loca les, | |||
// so we need to find out which locale actually worked. | // so we need to find out which locale actually worked. | |||
if (!empty($temp_locale)) | if (!empty($locale)) | |||
{ | { | |||
$old_locale = setlocale(LC_TIME, '0'); | $old_locale = setlocale(LC_TIME, '0'); | |||
$new_locale = setlocale(LC_TIME, $temp_locale); | $new_locale = setlocale(LC_TIME, $locale); | |||
} | } | |||
elseif ($server_os == "windows") | elseif ($server_os == "windows") | |||
{ | { | |||
// If we are running Windows we have to set the locale again in case another script | // If we are running Windows we have to set the locale again in case another script | |||
// running in the same process has changed the locale since we first set it. See the | // running in the same process has changed the locale since we first set it. See the | |||
// warning on the PHP manual page for setlocale(): | // warning on the PHP manual page for setlocale(): | |||
// | // | |||
// "The locale information is maintained per process, not per thread. If you are | // "The locale information is maintained per process, not per thread. If you are | |||
// running PHP on a multithreaded server API like IIS or Apache on Windows, you may | // running PHP on a multithreaded server API like IIS or Apache on Windows, you may | |||
// experience sudden changes in locale settings while a script is running, t hough | // experience sudden changes in locale settings while a script is running, t hough | |||
skipping to change at line 896 | skipping to change at line 897 | |||
$ampm = date('a', $time); | $ampm = date('a', $time); | |||
} | } | |||
$format = preg_replace('/%p/', $ampm, $format); | $format = preg_replace('/%p/', $ampm, $format); | |||
} | } | |||
$result = strftime($format, $time); | $result = strftime($format, $time); | |||
$result = System::utf8ConvertFromLocale($result, $new_locale); | $result = System::utf8ConvertFromLocale($result, $new_locale); | |||
// Restore the original locale | // Restore the original locale | |||
if (!empty($temp_locale)) | if (!empty($locale)) | |||
{ | { | |||
setlocale(LC_TIME, $old_locale); | setlocale(LC_TIME, $old_locale); | |||
} | } | |||
return $result; | return $result; | |||
} | } | |||
// Converts a single strftime() format character into an equivalent | ||||
// pattern for use with IntlDateFormatter. | ||||
// See https://www.php.net/manual/en/function.strftime.php and | ||||
// https://unicode-org.github.io/icu/userguide/format_parse/datetime/ | ||||
function get_equivalent_pattern(string $format, string $locale) : string | ||||
{ | ||||
$map = array( | ||||
// Day | ||||
'a' => 'ccc', | ||||
'A' => 'cccc', | ||||
'd' => 'dd', | ||||
'e' => 'd', | ||||
'j' => 'D', // not an exact equivalent: 'j' has leading zeros | ||||
// 'u' not supported | ||||
// 'w' not supported | ||||
// Week | ||||
// 'U' not supported | ||||
// 'V' not supported | ||||
// 'W' not supported | ||||
// Month | ||||
'b' => 'LLL', | ||||
'B' => 'LLLL', | ||||
'h' => 'LLL', | ||||
'm' => 'LL', | ||||
// Year | ||||
// 'C' not supported | ||||
// 'g' not supported | ||||
// 'G' not supported | ||||
'y' => 'yy', | ||||
'Y' => 'y', | ||||
// Time | ||||
'H' => 'HH', | ||||
'k' => 'H', | ||||
'I' => 'hh', | ||||
// 'l' not supported | ||||
'M' => 'mm', | ||||
'p' => 'a', // not an exact equivalent | ||||
'P' => 'a', // not an exact equivalent | ||||
'r' => 'hh:mm:ss a', | ||||
'R' => 'HH:mm', | ||||
'S' => 'ss', | ||||
'T' => 'HH:mm:ss', | ||||
// 'X' see below | ||||
'z' => 'Z', | ||||
'Z' => 'z', | ||||
// Time and Date Stamps | ||||
// 'c' see below | ||||
'D' => 'LL/dd/yy', | ||||
'F' => 'y-LL-dd' | ||||
// 's' not supported | ||||
// 'x' see below | ||||
); | ||||
// The simple case - there's a near equivalent | ||||
if (isset($map[$format])) | ||||
{ | ||||
return $map[$format]; | ||||
} | ||||
// More complicated cases - the pattern is locale dependent | ||||
switch ($format) | ||||
{ | ||||
case 'X': | ||||
$date_type = IntlDateFormatter::NONE; | ||||
$time_type = IntlDateFormatter::MEDIUM; | ||||
break; | ||||
case 'c': | ||||
$date_type = IntlDateFormatter::MEDIUM; | ||||
$time_type = IntlDateFormatter::LONG; | ||||
break; | ||||
case 'x': | ||||
$date_type = IntlDateFormatter::SHORT; | ||||
$time_type = IntlDateFormatter::NONE; | ||||
break; | ||||
default: | ||||
throw new \Exception("Cannot convert format '%$format'"); | ||||
break; | ||||
} | ||||
$formatter = new IntlDateFormatter( | ||||
$locale, | ||||
$date_type, | ||||
$time_type | ||||
); | ||||
return $formatter->getPattern(); | ||||
} | ||||
// Convert a strftime() format, which can contain a mixture of formatters and te | ||||
xt, | ||||
// into an IntlDateFormatter pattern | ||||
function format_convert(string $format, string $locale) : string | ||||
{ | ||||
// Trivial case | ||||
if (!isset($format) || ($format === '')) | ||||
{ | ||||
return $format; | ||||
} | ||||
// Split the string, which may contain multibyte characters, into an array and | ||||
// then iterate through the array | ||||
$chars = preg_split('//u', $format, -1, PREG_SPLIT_NO_EMPTY); | ||||
$char = current($chars); | ||||
$text = ''; | ||||
$result = ''; | ||||
do | ||||
{ | ||||
if ($char == '%') | ||||
{ | ||||
if (false === ($next_char = next($chars))) | ||||
{ | ||||
trigger_error("Badly formed format '$format'", E_USER_NOTICE); | ||||
} | ||||
elseif (in_array($next_char, array('n', 't', '%'))) | ||||
{ | ||||
// Escaped characters | ||||
switch ($next_char) | ||||
{ | ||||
case 'n': | ||||
$text_char = "\n"; | ||||
break; | ||||
case 't': | ||||
$text_char = "\t"; | ||||
break; | ||||
case '%': | ||||
$text_char = '%'; | ||||
break; | ||||
} | ||||
$text .= $text_char; | ||||
} | ||||
else | ||||
{ | ||||
// It's a format character | ||||
// If there's a text string outstanding then append that to the result | ||||
if ($text !== '') | ||||
{ | ||||
// Don't need to escape characters outside ['a'..'z'] and ['A'..'Z'] b | ||||
ut it's simpler to do so | ||||
$result .= "'$text'"; | ||||
$text = ''; | ||||
} | ||||
// Followed by the ICU equivalent of the format character | ||||
$result .= get_equivalent_pattern($next_char, $locale); | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
$text .= ($char == "'") ? "''" : $char; // Escape single quotes | ||||
} | ||||
} while (false !== ($char = next($chars))); | ||||
// Add any remaining text | ||||
if ($text !== '') | ||||
{ | ||||
// Don't need to escape characters outside ['a'..'z'] and ['A'..'Z'] but it' | ||||
s simpler to do so | ||||
$result .= "'$text'"; | ||||
} | ||||
return $result; | ||||
} | ||||
// Format a local time/date according to locale settings, returning the | ||||
// result as a UTF-8 string. This function is based on IntlDateFormatter. | ||||
// $time can be an int or a float (union type declarations not supported until P | ||||
HP 8.0) | ||||
// $locale can either be a string or an array of locales, but if an array | ||||
// then only the first is used. | ||||
// If $locale is not set then the current locale is used. | ||||
function date_formatter_intl(string $format, $time, $locale=null) : string | ||||
{ | ||||
if (!isset($locale)) | ||||
{ | ||||
$locale = get_mrbs_locale(); | ||||
} | ||||
if (is_array($locale)) | ||||
{ | ||||
$locale = $locale[0]; | ||||
} | ||||
$pattern = format_convert($format, $locale); | ||||
$formatter = new IntlDateFormatter( | ||||
$locale, | ||||
IntlDateFormatter::FULL, | ||||
IntlDateFormatter::FULL, | ||||
null, | ||||
null, | ||||
$pattern | ||||
); | ||||
return $formatter->format($time); | ||||
} | ||||
// Format a local time/date according to locale settings, returning the | ||||
// result as a UTF-8 string. | ||||
// $time can be an int or a float (union type declarations not supported until P | ||||
HP 8.0) | ||||
// $locale can either be a string or an array of locales. If $locale | ||||
// is not set then the current locale is used. | ||||
function utf8_strftime(string $format, $time, $locale=null) : string | ||||
{ | ||||
// strftime() is deprecated from PHP 8.1, so we use IntlDateFormatter | ||||
// if we can. | ||||
// TODO: This solution just translates strftime formats into the nearest | ||||
// TODO: equivalent IntlDateFormatter patterns. However it would be better | ||||
// TODO: to rewrite MRBS so that IntlDateFormatter patterns are used if | ||||
// TODO: as they are more flexible and better suited to reflecting locales. | ||||
if (class_exists('IntlDateFormatter')) | ||||
{ | ||||
return date_formatter_intl($format, $time, $locale); | ||||
} | ||||
else | ||||
{ | ||||
return date_formatter_strftime($format, $time, $locale); | ||||
} | ||||
} | ||||
// UTF-8 compatible substr function obtained from a contribution by | // UTF-8 compatible substr function obtained from a contribution by | |||
// "frank at jkelloggs dot dk" in the PHP online manual for substr() | // "frank at jkelloggs dot dk" in the PHP online manual for substr() | |||
function utf8_substr_old($str, $start, $length=null) | function utf8_substr_old($str, $start, $length=null) | |||
{ | { | |||
preg_match_all("/./su", $str, $ar); | preg_match_all("/./su", $str, $ar); | |||
return join("", array_slice($ar[0], $start, $length)); | return join("", array_slice($ar[0], $start, $length)); | |||
} | } | |||
// UTF-8 compatible substr function | // UTF-8 compatible substr function | |||
function utf8_substr($str, $start, $length=null) | function utf8_substr(string $str, int $start, ?int $length=null) | |||
{ | { | |||
if (!isset($length)) | if (!isset($length)) | |||
{ | { | |||
// We cannot set $length to PHP_INT_MAX because there is a bug (#42101) | // We cannot set $length to PHP_INT_MAX because there is a bug (#42101) | |||
// in mb_substr() when using PHP_INT_MAX on 64 bit Linux systems. See | // in mb_substr() when using PHP_INT_MAX on 64 bit Linux systems. See | |||
// https://bugs.php.net/bug.php?id=42101. Note also that passing NULL | // https://bugs.php.net/bug.php?id=42101. Note also that passing NULL | |||
// for the length in mb_substr() causes mb_substr to use a length of 0. | // for the length in mb_substr() causes mb_substr to use a length of 0. | |||
// See the user contributed notes on the PHP mb_substr() manual page. | // See the user contributed notes on the PHP mb_substr() manual page. | |||
$length = utf8_strlen($str); | $length = utf8_strlen($str); | |||
} | } | |||
End of changes. 19 change blocks. | ||||
31 lines changed or deleted | 260 lines changed or added |