From caaf8eb2d1d4efc227e5f6163c2fc5f61233bcf6 Mon Sep 17 00:00:00 2001 From: Michael Beck Date: Fri, 16 Oct 2026 03:16:17 -0400 Subject: [PATCH 1/2] fix(smarty): drop mb_regex_encoding()/mb_split() from smarty_mb_str_replace() PHP 8.6 deprecates the mb_ereg family (the bundled oniguruma library is no longer maintained), so every |replace and |escape modifier call logged "Function mb_regex_encoding() is deprecated" three times per invocation. The single-string branch now uses str_replace(), which is byte-exact and safe for UTF-8 and any ASCII-superset charset. This matches Smarty 5, which removed the helper in favour of str_replace(). Smarty 4.5.8 is the end of the 4.x line, so the committed vendor copy carries the patch. --- docs/changelog.270.txt | 1 + .../libs/plugins/shared.mb_str_replace.php | 40 ++----------------- 2 files changed, 4 insertions(+), 37 deletions(-) diff --git a/docs/changelog.270.txt b/docs/changelog.270.txt index c918e8215..5f68c841f 100644 --- a/docs/changelog.270.txt +++ b/docs/changelog.270.txt @@ -10,6 +10,7 @@ Bug fixes: - xoops_aw_encode() and xoops_aw_decode() take their element by reference, so the array_walk() calls that encode and decode XOBJ_DTYPE_UNICODE_ARRAY values act on the array; write.php and XoopsObject::setVar() serialize the walked array instead of array_walk()'s boolean, and XoopsObject::getVar() returns the decoded array instead of true (include/functions.encoding.php, class/model/write.php, kernel/object.php) - a XOBJ_DTYPE_UNICODE_ARRAY value reloads as the array that was stored: assignVar() keeps the serialized string as is instead of url-decoding it, which broke the string lengths of elements such as café or a+b and made getVar() return [], and XoopsModelWrite::cleanVars() no longer addslashes() the elements before url-encoding them, which stored the backslashes (kernel/object.php, class/model/write.php, tests/unit/htdocs/kernel/XoopsObjectUnicodeArrayTest.php) - xoops_getUrlDomain() checks the host key only when parse_url() returned an array (include/functions.php) +- the |replace and |escape modifiers no longer emit PHP 8.6 deprecations: Smarty's smarty_mb_str_replace() uses str_replace() instead of mb_regex_encoding()/mb_split(), which are deprecated since PHP 8.6 and were dropped by Smarty 5 (xoops_lib/vendor/smarty/smarty/libs/plugins/shared.mb_str_replace.php) - templates can use |sprintf without a Smarty 4 deprecation notice: XoopsTpl registers sprintf as a modifier, as it already does for strstr, trim and file_exists (class/template.php) (#264) Libraries and assets: diff --git a/htdocs/xoops_lib/vendor/smarty/smarty/libs/plugins/shared.mb_str_replace.php b/htdocs/xoops_lib/vendor/smarty/smarty/libs/plugins/shared.mb_str_replace.php index 7e85f7aae..3b01dcb1f 100644 --- a/htdocs/xoops_lib/vendor/smarty/smarty/libs/plugins/shared.mb_str_replace.php +++ b/htdocs/xoops_lib/vendor/smarty/smarty/libs/plugins/shared.mb_str_replace.php @@ -44,43 +44,9 @@ function smarty_mb_str_replace($search, $replace, $subject, &$count = 0) } } } else { - $mb_reg_charset = mb_regex_encoding(); - // Check if mbstring regex is using UTF-8 - $reg_is_unicode = !strcasecmp($mb_reg_charset, "UTF-8"); - if(!$reg_is_unicode) { - // ...and set to UTF-8 if not - mb_regex_encoding("UTF-8"); - } - - // See if charset used by Smarty is matching one used by regex... - $current_charset = mb_regex_encoding(); - $convert_result = (bool)strcasecmp(Smarty::$_CHARSET, $current_charset); - if($convert_result) { - // ...convert to it if not. - $subject = mb_convert_encoding($subject, $current_charset, Smarty::$_CHARSET); - $search = mb_convert_encoding($search, $current_charset, Smarty::$_CHARSET); - $replace = mb_convert_encoding($replace, $current_charset, Smarty::$_CHARSET); - } - - $parts = mb_split(preg_quote($search), $subject ?? "") ?: array(); - // If original regex encoding was not unicode... - if(!$reg_is_unicode) { - // ...restore original regex encoding to avoid breaking the system. - mb_regex_encoding($mb_reg_charset); - } - if($parts === false) { - // This exception is thrown if call to mb_split failed. - // Usually it happens, when $search or $replace are not valid for given mb_regex_encoding(). - // There may be other cases for it to fail, please file an issue if you find a reproducible one. - throw new SmartyException("Source string is not a valid $current_charset sequence (probably)"); - } - - $count = count($parts) - 1; - $subject = implode($replace, $parts); - // Convert results back to charset used by Smarty, if needed. - if($convert_result) { - $subject = mb_convert_encoding($subject, Smarty::$_CHARSET, $current_charset); - } + // mb_regex_encoding()/mb_split() are deprecated since PHP 8.6 (libmbfl oniguruma). + // str_replace() is byte-exact and safe for UTF-8 and any ASCII-superset charset. + $subject = str_replace($search, $replace, $subject ?? '', $count); } return $subject; } From 115a6fab326b3dc9a02f98bf9a6e718410e9fe05 Mon Sep 17 00:00:00 2001 From: Michael Beck Date: Fri, 16 Oct 2026 03:32:26 -0400 Subject: [PATCH 2/2] fix(smarty): keep the UTF-8 round-trip for non-UTF-8 charsets in smarty_mb_str_replace() str_replace() is only character-safe on UTF-8, which is self-synchronizing. For any other Smarty charset (EUC-JP, GB2312, ...) the subject, search and replacement are converted to UTF-8 first and the result converted back, as the original mb_regex_encoding() code did, so a search can never match across a character boundary. Also correct the changelog: Smarty 5.8.5 still ships the mb_split() version of this helper; the functions are deprecated in PHP 8.6 and scheduled for removal in PHP 9.0. --- docs/changelog.270.txt | 2 +- .../smarty/libs/plugins/shared.mb_str_replace.php | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/changelog.270.txt b/docs/changelog.270.txt index 5f68c841f..9cff7db27 100644 --- a/docs/changelog.270.txt +++ b/docs/changelog.270.txt @@ -10,7 +10,7 @@ Bug fixes: - xoops_aw_encode() and xoops_aw_decode() take their element by reference, so the array_walk() calls that encode and decode XOBJ_DTYPE_UNICODE_ARRAY values act on the array; write.php and XoopsObject::setVar() serialize the walked array instead of array_walk()'s boolean, and XoopsObject::getVar() returns the decoded array instead of true (include/functions.encoding.php, class/model/write.php, kernel/object.php) - a XOBJ_DTYPE_UNICODE_ARRAY value reloads as the array that was stored: assignVar() keeps the serialized string as is instead of url-decoding it, which broke the string lengths of elements such as café or a+b and made getVar() return [], and XoopsModelWrite::cleanVars() no longer addslashes() the elements before url-encoding them, which stored the backslashes (kernel/object.php, class/model/write.php, tests/unit/htdocs/kernel/XoopsObjectUnicodeArrayTest.php) - xoops_getUrlDomain() checks the host key only when parse_url() returned an array (include/functions.php) -- the |replace and |escape modifiers no longer emit PHP 8.6 deprecations: Smarty's smarty_mb_str_replace() uses str_replace() instead of mb_regex_encoding()/mb_split(), which are deprecated since PHP 8.6 and were dropped by Smarty 5 (xoops_lib/vendor/smarty/smarty/libs/plugins/shared.mb_str_replace.php) +- the |replace and |escape modifiers no longer emit PHP 8.6 deprecations: Smarty's smarty_mb_str_replace() uses str_replace() instead of mb_regex_encoding()/mb_split(), which are deprecated since PHP 8.6 and scheduled for removal in PHP 9.0; non-UTF-8 charsets are still round-tripped through UTF-8 (xoops_lib/vendor/smarty/smarty/libs/plugins/shared.mb_str_replace.php) - templates can use |sprintf without a Smarty 4 deprecation notice: XoopsTpl registers sprintf as a modifier, as it already does for strstr, trim and file_exists (class/template.php) (#264) Libraries and assets: diff --git a/htdocs/xoops_lib/vendor/smarty/smarty/libs/plugins/shared.mb_str_replace.php b/htdocs/xoops_lib/vendor/smarty/smarty/libs/plugins/shared.mb_str_replace.php index 3b01dcb1f..c9a4a242d 100644 --- a/htdocs/xoops_lib/vendor/smarty/smarty/libs/plugins/shared.mb_str_replace.php +++ b/htdocs/xoops_lib/vendor/smarty/smarty/libs/plugins/shared.mb_str_replace.php @@ -44,9 +44,19 @@ function smarty_mb_str_replace($search, $replace, $subject, &$count = 0) } } } else { - // mb_regex_encoding()/mb_split() are deprecated since PHP 8.6 (libmbfl oniguruma). - // str_replace() is byte-exact and safe for UTF-8 and any ASCII-superset charset. + // mb_regex_encoding()/mb_split() are deprecated since PHP 8.6 (bundled oniguruma is unmaintained). + // str_replace() is byte-exact and safe on UTF-8, which is self-synchronizing; other multibyte + // charsets (EUC-JP, GB2312, ...) are round-tripped through UTF-8 so no match spans a character boundary. + $convert = (bool)strcasecmp(Smarty::$_CHARSET, 'UTF-8'); + if ($convert) { + $subject = mb_convert_encoding($subject ?? '', 'UTF-8', Smarty::$_CHARSET); + $search = mb_convert_encoding($search, 'UTF-8', Smarty::$_CHARSET); + $replace = mb_convert_encoding($replace, 'UTF-8', Smarty::$_CHARSET); + } $subject = str_replace($search, $replace, $subject ?? '', $count); + if ($convert) { + $subject = mb_convert_encoding($subject, Smarty::$_CHARSET, 'UTF-8'); + } } return $subject; }