diff --git a/README.md b/README.md index 7948832..da11ab4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # Mojibake Repair (Temporary) -Version 1.1.0 replaces the original whole-column SQL conversion with targeted -replacements for Czech letters misdecoded as ISO-8859-1 or Windows-1252. +Version 1.1.1 uses targeted replacements for Czech letters, common punctuation, +symbols and nonbreaking spaces misdecoded as ISO-8859-1 or Windows-1252. It extends +the serialization-safe repair introduced in version 1.1.0. ## Why version 1.0.0 could break fonts @@ -26,7 +27,10 @@ serialization risk in its [migration guide](https://developer.wordpress.org/adva ## Behavior and limits -- Repairs recognized Czech mojibake sequences without transcoding whole values. +- Repairs recognized mojibake sequences without transcoding whole values. + Targets include Czech letters and `– — ‘ ’ ‚ “ ” „ … • « » ‹ › € ™ © ® °`, + plus nonbreaking spaces. For example, `–` becomes `–`, `’` becomes `’`, + and `…` becomes `…`. Both Latin-1 and Windows-1252 forms are covered. Other corruption patterns and JSON `\uXXXX` escapes are outside this repair's scope. A sequence intentionally shown as a mojibake example will also match, so review the preview. @@ -50,7 +54,7 @@ serialization risk in its [migration guide](https://developer.wordpress.org/adva ## Local verification Run `php -l wp-mojibake-repair.php` and `php tests/repair.php`. -The regression script requires PDO SQLite and iconv. It checks Czech text, +The regression script requires PDO SQLite and iconv. It checks Czech text, punctuation, serialized typography settings, preserved CSS/JSON, skipped unsafe values, batching, preview approvals, rollback, and cache invalidation. Database-flow checks use a SQLite adapter; they do not replace verification on WordPress/MySQL. diff --git a/tests/repair.php b/tests/repair.php index 00505b7..f3eb9fb 100644 --- a/tests/repair.php +++ b/tests/repair.php @@ -39,6 +39,27 @@ same($czech, fix(broken($czech)), 'Latin-1 mojibake, all Czech letters'); // These letters exercise printable Windows-1252 mappings including ™, ¾ and œ. $windows = 'ěňřšťůžČĎĚŇŘŠŤŮŽ'; same($windows, fix(iconv('Windows-1252', 'UTF-8', $windows)), 'Windows-1252 mojibake'); +$punctuation = array( + '–' => '–', '—' => '—', '‘' => '‘', '’' => '’', '‚' => '‚', + '“' => '“', "â€\u{009D}" => '”', '„' => '„', '…' => '…', '•' => '•', + '«' => '«', '»' => '»', '‹' => '‹', '›' => '›', '€' => '€', + 'â„¢' => '™', '©' => '©', '®' => '®', '°' => '°', "Â\u{00A0}" => "\u{00A0}", +); +foreach ($punctuation as $damaged => $clean) { + same($clean, fix($damaged), 'Windows-1252 punctuation: ' . $clean); + same($clean, fix(broken($clean)), 'Latin-1 punctuation: ' . $clean); +} +$clean_punctuation = implode('', $punctuation); +same($clean_punctuation, fix($clean_punctuation), 'Correct punctuation and nonbreaking space unchanged'); +$damaged_punctuation = implode('', array_keys($punctuation)); +same($clean_punctuation, fix(fix($damaged_punctuation)), 'Repeated punctuation repair is stable'); +$punctuation_settings = array('font_family' => 'Open Sans', 'label' => $damaged_punctuation); +$punctuation_expected = array('font_family' => 'Open Sans', 'label' => $clean_punctuation); +same(serialize($punctuation_expected), fix(serialize($punctuation_settings)), 'Punctuation repair preserves serialized typography settings'); +$punctuation_css = 'body{font-family:"Open Sans"}/* – ’ … */'; +same('body{font-family:"Open Sans"}/* – ’ … */', fix($punctuation_css), 'Punctuation in CSS preserves styling'); +$punctuation_json = '{"font":"Open Sans","url":"https:\/\/example.test","label":"– ’ …","escaped":"\\u2013"}'; +same(strtr($punctuation_json, $punctuation), fix($punctuation_json), 'Punctuation in JSON preserves escaping'); $correct = $czech . ' 日本語 😀 € “quoted” — ß ö ñ'; same($correct, fix($correct), 'Correct multilingual Unicode is byte-for-byte unchanged'); same($correct . ' Příliš', fix($correct . ' ' . broken('Příliš')), 'Mixed clean and damaged text'); diff --git a/wp-mojibake-repair.php b/wp-mojibake-repair.php index 84853ca..bceae52 100644 --- a/wp-mojibake-repair.php +++ b/wp-mojibake-repair.php @@ -1,8 +1,8 @@ '˜', 0x99 => '™', 0x9a => 'š', 0x9b => '›', 0x9c => 'œ', 0x9e => 'ž', 0x9f => 'Ÿ', ); - foreach (preg_split('//u', 'áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ', -1, PREG_SPLIT_NO_EMPTY) as $letter) { + // Only these target characters generate replacements. The Windows + // table above describes misdecoded bytes; it is not a target list. + $targets = 'áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ' + . '–—‘’‚“”„…•«»‹›€™©®°' . "\xc2\xa0"; // Nonbreaking space. + foreach (preg_split('//u', $targets, -1, PREG_SPLIT_NO_EMPTY) as $letter) { $latin = ''; $cp1252 = ''; foreach (unpack('C*', $letter) as $byte) { @@ -464,7 +468,7 @@ final class WP_Mojibake_Repair_Temporary { echo '

Preview

'; echo '

' . number_format_i18n($preview['total']) . ' differing column values found. Only the samples below will be displayed; the confirmed update covers all counted candidates.

'; - echo '

Only recognized Czech mojibake sequences are replaced. Correct Unicode is preserved, and serialized string lengths are updated. Review the samples to confirm that these replacements match your content.

'; + echo '

Only recognized mojibake sequences for Czech letters, common punctuation, symbols and nonbreaking spaces are replaced. Correct Unicode is preserved, and serialized string lengths are updated. Review the samples to confirm that these replacements match your content.

'; if ($preview['skipped'] > 0) { echo '

' . number_format_i18n($preview['skipped']) . ' values were skipped. They contain repair candidates but have malformed or unsupported data (such as serialized objects or references). Already broken settings need recovery from a backup.

'; }