Expand mojibake repair to punctuation and symbols

This commit is contained in:
2026-09-08 15:30:59 +02:00
parent 25de2a13f9
commit 2e4dd7417a
3 changed files with 38 additions and 9 deletions
+8 -4
View File
@@ -1,7 +1,8 @@
# Mojibake Repair (Temporary) # Mojibake Repair (Temporary)
Version 1.1.0 replaces the original whole-column SQL conversion with targeted Version 1.1.1 uses targeted replacements for Czech letters, common punctuation,
replacements for Czech letters misdecoded as ISO-8859-1 or Windows-1252. 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 ## 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 ## 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 Other corruption patterns and JSON `\uXXXX` escapes are outside this repair's
scope. A sequence intentionally shown as a mojibake example will also match, scope. A sequence intentionally shown as a mojibake example will also match,
so review the preview. so review the preview.
@@ -50,7 +54,7 @@ serialization risk in its [migration guide](https://developer.wordpress.org/adva
## Local verification ## Local verification
Run `php -l wp-mojibake-repair.php` and `php tests/repair.php`. 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, serialized typography settings, preserved CSS/JSON, skipped unsafe values,
batching, preview approvals, rollback, and cache invalidation. Database-flow batching, preview approvals, rollback, and cache invalidation. Database-flow
checks use a SQLite adapter; they do not replace verification on WordPress/MySQL. checks use a SQLite adapter; they do not replace verification on WordPress/MySQL.
+21
View File
@@ -39,6 +39,27 @@ same($czech, fix(broken($czech)), 'Latin-1 mojibake, all Czech letters');
// These letters exercise printable Windows-1252 mappings including ™, ¾ and œ. // These letters exercise printable Windows-1252 mappings including ™, ¾ and œ.
$windows = 'ěňřšťůžČĎĚŇŘŠŤŮŽ'; $windows = 'ěňřšťůžČĎĚŇŘŠŤŮŽ';
same($windows, fix(iconv('Windows-1252', 'UTF-8', $windows)), 'Windows-1252 mojibake'); 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” — ß ö ñ'; $correct = $czech . ' 日本語 😀 € “quoted” — ß ö ñ';
same($correct, fix($correct), 'Correct multilingual Unicode is byte-for-byte unchanged'); 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'); same($correct . ' Příliš', fix($correct . ' ' . broken('Příliš')), 'Mixed clean and damaged text');
+9 -5
View File
@@ -1,8 +1,8 @@
<?php <?php
/** /**
* Plugin Name: Mojibake Repair (Temporary) * Plugin Name: Mojibake Repair (Temporary)
* Description: Previews and repairs Czech UTF-8 mojibake while preserving serialized WordPress settings. * Description: Previews and repairs Czech text and punctuation mojibake while preserving serialized WordPress settings.
* Version: 1.1.0 * Version: 1.1.1
* Requires PHP: 7.4 * Requires PHP: 7.4
*/ */
@@ -14,7 +14,7 @@ final class WP_Mojibake_Repair_Temporary
const SAMPLE_LIMIT = 5; const SAMPLE_LIMIT = 5;
const SAMPLE_LENGTH = 300; const SAMPLE_LENGTH = 300;
const APPROVAL_TTL = 600; const APPROVAL_TTL = 600;
const REPAIR_VERSION = 2; const REPAIR_VERSION = 3;
const BATCH_SIZE = 200; const BATCH_SIZE = 200;
public static function init() public static function init()
@@ -297,7 +297,11 @@ final class WP_Mojibake_Repair_Temporary
0x98 => '˜', 0x99 => '™', 0x9a => 'š', 0x9b => '›', 0x9c => 'œ', 0x98 => '˜', 0x99 => '™', 0x9a => 'š', 0x9b => '›', 0x9c => 'œ',
0x9e => 'ž', 0x9f => 'Ÿ', 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 = ''; $latin = '';
$cp1252 = ''; $cp1252 = '';
foreach (unpack('C*', $letter) as $byte) { foreach (unpack('C*', $letter) as $byte) {
@@ -464,7 +468,7 @@ final class WP_Mojibake_Repair_Temporary
{ {
echo '<h2>Preview</h2>'; echo '<h2>Preview</h2>';
echo '<p><strong>' . number_format_i18n($preview['total']) . '</strong> differing column values found. Only the samples below will be displayed; the confirmed update covers all counted candidates.</p>'; echo '<p><strong>' . number_format_i18n($preview['total']) . '</strong> differing column values found. Only the samples below will be displayed; the confirmed update covers all counted candidates.</p>';
echo '<p>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.</p>'; echo '<p>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.</p>';
if ($preview['skipped'] > 0) { if ($preview['skipped'] > 0) {
echo '<div class="notice notice-warning"><p><strong>' . number_format_i18n($preview['skipped']) . ' values were skipped.</strong> They contain repair candidates but have malformed or unsupported data (such as serialized objects or references). Already broken settings need recovery from a backup.</p></div>'; echo '<div class="notice notice-warning"><p><strong>' . number_format_i18n($preview['skipped']) . ' values were skipped.</strong> They contain repair candidates but have malformed or unsupported data (such as serialized objects or references). Already broken settings need recovery from a backup.</p></div>';
} }