3 declare(strict_types
=1);
7 use function array_intersect
;
8 use function array_map
;
14 use function function_exists
;
17 use function mb_convert_encoding
;
18 use function mb_convert_kana
;
19 use function mb_detect_encoding
;
20 use function mb_list_encodings
;
21 use function strtolower
;
26 * Encoding conversion helper class
31 * None encoding conversion engine
33 public const ENGINE_NONE
= 0;
36 * iconv encoding conversion engine
38 public const ENGINE_ICONV
= 1;
41 * mbstring encoding conversion engine
43 public const ENGINE_MB
= 3;
46 * Chosen encoding engine
48 private static int|
null $engine = null;
51 * Map of conversion engine configurations
53 * Each entry contains:
55 * - function to detect
57 * - extension name to warn when missing
61 private static array $enginemap = [
62 'iconv' => ['iconv', self
::ENGINE_ICONV
, 'iconv'],
63 'mb' => ['mb_convert_encoding', self
::ENGINE_MB
, 'mbstring'],
64 'none' => ['isset', self
::ENGINE_NONE
, ''],
68 * Order of automatic detection of engines
72 private static array $engineorder = ['iconv', 'mb'];
75 * Kanji encodings list
77 private static string $kanjiEncodings = 'ASCII,SJIS,EUC-JP,JIS';
80 * Initializes encoding engine detecting available backends.
82 public static function initEngine(): void
85 $config = Config
::getInstance();
86 if (isset($config->settings
['RecodingEngine'])) {
87 $engine = $config->settings
['RecodingEngine'];
90 /* Use user configuration */
91 if (isset(self
::$enginemap[$engine])) {
92 if (function_exists(self
::$enginemap[$engine][0])) {
93 self
::$engine = self
::$enginemap[$engine][1];
98 Core
::warnMissingExtension(self
::$enginemap[$engine][2]);
102 foreach (self
::$engineorder as $engine) {
103 if (function_exists(self
::$enginemap[$engine][0])) {
104 self
::$engine = self
::$enginemap[$engine][1];
110 /* Fallback to none conversion */
111 self
::$engine = self
::ENGINE_NONE
;
115 * Setter for engine. Use with caution, mostly useful for testing.
117 * @param int $engine Engine encoding
119 public static function setEngine(int $engine): void
121 self
::$engine = $engine;
125 * Checks whether there is any charset conversion supported
127 public static function isSupported(): bool
129 if (self
::$engine === null) {
133 return self
::$engine != self
::ENGINE_NONE
;
137 * Converts encoding of text according to parameters with detected
138 * conversion function.
140 * @param string $srcCharset source charset
141 * @param string $destCharset target charset
142 * @param string $what what to convert
144 * @return string converted text
146 public static function convertString(
151 if ($srcCharset === $destCharset) {
155 if (self
::$engine === null) {
159 return match (self
::$engine) {
160 self
::ENGINE_ICONV
=> iconv(
162 $destCharset . (Config
::getInstance()->settings
['IconvExtraParams'] ??
''), $what,
164 self
::ENGINE_MB
=> mb_convert_encoding($what, $destCharset, $srcCharset),
170 * Detects whether Kanji encoding is available
172 public static function canConvertKanji(): bool
174 return $GLOBALS['lang'] === 'ja';
178 * Setter for Kanji encodings. Use with caution, mostly useful for testing.
180 public static function getKanjiEncodings(): string
182 return self
::$kanjiEncodings;
186 * Setter for Kanji encodings. Use with caution, mostly useful for testing.
188 * @param string $value Kanji encodings list
190 public static function setKanjiEncodings(string $value): void
192 self
::$kanjiEncodings = $value;
196 * Reverses SJIS & EUC-JP position in the encoding codes list
198 public static function kanjiChangeOrder(): void
200 $parts = explode(',', self
::$kanjiEncodings);
201 if ($parts[1] === 'EUC-JP') {
202 self
::$kanjiEncodings = 'ASCII,SJIS,EUC-JP,JIS';
207 self
::$kanjiEncodings = 'ASCII,EUC-JP,SJIS,JIS';
211 * Kanji string encoding convert
213 * @param string $str the string to convert
214 * @param string $enc the destination encoding code
215 * @param string $kana set 'kana' convert to JIS-X208-kana
217 * @return string the converted string
219 public static function kanjiStrConv(string $str, string $enc, string $kana): string
221 if ($enc === '' && $kana === '') {
225 $stringEncoding = mb_detect_encoding($str, self
::$kanjiEncodings);
226 if ($stringEncoding === false) {
227 $stringEncoding = 'utf-8';
230 if ($kana === 'kana') {
231 $dist = mb_convert_kana($str, 'KV', $stringEncoding);
235 if ($stringEncoding !== $enc && $enc !== '') {
236 return mb_convert_encoding($str, $enc, $stringEncoding);
243 * Kanji file encoding convert
245 * @param string $file the name of the file to convert
246 * @param string $enc the destination encoding code
247 * @param string $kana set 'kana' convert to JIS-X208-kana
249 * @return string the name of the converted file
251 public static function kanjiFileConv(string $file, string $enc, string $kana): string
253 if ($enc === '' && $kana === '') {
257 $tmpfname = (string) tempnam(Config
::getInstance()->getUploadTempDir(), $enc);
258 $fpd = fopen($tmpfname, 'wb');
259 if ($fpd === false) {
263 $fps = fopen($file, 'r');
264 if ($fps === false) {
268 self
::kanjiChangeOrder();
269 while (! feof($fps)) {
270 $line = fgets($fps, 4096);
271 if ($line === false) {
275 $dist = self
::kanjiStrConv($line, $enc, $kana);
279 self
::kanjiChangeOrder();
288 * Defines radio form fields to switch between encoding modes
290 * @return string HTML code for the radio controls
292 public static function kanjiEncodingForm(): string
294 $template = new Template();
296 return $template->render('encoding/kanji_encoding_form');
300 * Lists available encodings.
304 public static function listEncodings(): array
306 if (self
::$engine === null) {
310 /* Most engines do not support listing */
311 $config = Config
::getInstance();
312 if (self
::$engine != self
::ENGINE_MB
) {
313 return $config->settings
['AvailableCharsets'];
316 return array_intersect(
317 array_map(strtolower(...), mb_list_encodings()),
318 $config->settings
['AvailableCharsets'],