wp_kses() stoi za wp_kses_post(), czyszczeniem komentarzy, tekstem widgetów i każdym wywołaniem wp_kses( $html, $allowed ) w Twojej wtyczce. W trunku działa teraz na HTML API zamiast na wyrażeniach regularnych. Według raportu Dennisa Snella zmiana ma trafić do WordPressa 7.2 albo 7.3, zależnie od wyników testów. Poniżej: co zmienia się w wyniku, jak znaleźć własny kod, którego to dotyczy, i do czego służy tymczasowy filtr wyłączający zmianę.
Co dokładnie zmienia się w wp_kses()
Stara implementacja rozbierała znaczniki wyrażeniami regularnymi. Nowa czyta HTML tokenizerem z HTML API, dla każdego znacznika i atrybutu sprawdza, czy jest dozwolony, a potem zapisuje wynik od nowa. Pull request nosi tytuł “KSES: Reimplement with Tag Processor” (PR 13271) i jest powiązany z biletem Trac 66208. Według opisu PR nowy kod siedzi w funkcji wp_sanitize_html_kses().
Intencja z raportu jest jasna: wp_kses() nie ma być gorsze niż było. Raport mówi też, że wtyczki i motywy nie powinny wymagać zmian. Oba zdania mogą być prawdziwe, a Twoje testy i tak mogą zaświecić się na czerwono, bo “tak samo bezpiecznie” nie znaczy “te same bajty”. Ponowna serializacja to sedno zmiany, a ona zmienia stringi.
Źródło: Progress Report: wp_kses(), 7 października 2026.
Stan na 15 października 2026: zmiana jest już w trunku. Weszła 7 października 2026 jako changeset 64233 (commit na GitHubie), a opis commita mówi, że kod wywołujący funkcję nie powinien wymagać zmian. Docblock wp_kses() w trunku ma już adnotację @since 7.2.0, ale raport nadal mówi o 7.2 albo 7.3, a o tym, co trafi do wersji finalnej, decyduje okres testów, nie adnotacja. 9 października do kses.php weszły dwa kolejne commity: jeden normalizuje wszystkie delimitery bloków, drugi serializuje atrybuty JSON bloków tylko wtedy, gdy nie są puste (historia pliku).
Które dane wejściowe dają inny wynik
Raport podaje pary przed i po. Poniższy blok jest na nich wzorowany (moja tablica dozwolonych znaczników, wejścia z raportu). Zanim zaufasz mojemu przepisaniu, odpal je na własnym buildzie trunk.
<?php
// Modelled on the examples in the Make Core progress report.
$allowed = array(
'a' => array( 'href' => true ),
'code' => true,
'img' => array( 'src' => true, 'class' => true ),
);
wp_kses( 'Click on my <script>alert(1)</script>!', $allowed );
// legacy: Click on my alert(1)!
// new: Click on my !
wp_kses( 'The <IMG class=bike class="boat" src=\'vehicle.png\' /> & the wheels…', $allowed );
// legacy: The <IMG class="bike" src="vehicle.png" /> & the wheels…
// new: The <img class="bike" src="vehicle.png"> & the wheels…
wp_kses( 'Add <code>/?preview=true§ion=grilling</code>.', $allowed );
// legacy: Add <code>/?preview=true&section=grilling</code>.
// new: Add <code>/?preview=true§ion=grilling</code>.
wp_kses( 'Click on the <butto', $allowed );
// legacy: Click on the <butto
// new: Click on the (trailing space, nothing else)Raport grupuje różnice tak:
- Normalizacja. Nazwy znaczników trafiają do małych liter, atrybuty dostają podwójne cudzysłowy, powtórzony atrybut zostaje tylko pierwszy, a odwołania do znaków są zapisywane od nowa.
- Luźne nawiasy kątowe. Tekst typu
<$500albo>5yobył wcześniej połykany. Teraz jest zamieniany na<i>, więc tekst zostaje. - Niejednoznaczny ampersand. Nazwane odwołanie bez średnika, na przykład
§ionw adresie URL w węźle tekstowym, jest dekodowane tak jak w przeglądarce. Stąd w trzecim przykładzie wychodzi znak paragrafu. - Script i style. Gdy te elementy nie są dozwolone, ich zawartość znika razem ze znacznikami. Stary kod zostawiał zawartość jako widoczny tekst.
- Ucięty koniec danych. Niedokończony ostatni znacznik jest odrzucany, nie escapowany.
- Elementy specjalne. Tekst podobny do znacznika wewnątrz dozwolonego
<title>jest escapowany, a ten sam tekst wewnątrz<div>znika razem z nieznanym znacznikiem. - CDATA, PLAINTEXT i niedokończone elementy specjalne. Sekcje CDATA i zawartość
PLAINTEXTstają się zwykłym, escapowanym tekstem.<title>albo inny element specjalny, który przychodzi niedokończony, jest usuwany w całości. - SVG i MathML. Treść wymagająca złożonego parsowania powoduje, że funkcja przerywa i zwraca tylko to, co było przed tym miejscem. Raport pokazuje
<li>z MathML i zagnieżdżonym<span>, ucięty w tym punkcie. - Twarde limity. Część zniekształconych komentarzy,
NOSCRIPTi mylące elementy są teraz odrzucane. - Niezbilansowane znaczniki. Zostają bez zmian, bo istniejący kod na tym polega.
W polskim sklepie na WooCommerce typowi kandydaci do sprawdzenia to opisy produktów z wymiarami w rodzaju “<5 cm” albo “temperatura >60 stopni”, wklejone z arkusza dostawcy, oraz zaimportowane z hurtowni opisy z dużymi literami w znacznikach. Pierwsze wcześniej traciły fragment tekstu, drugie zmienią zapis po ponownej serializacji. To domysł o tym, gdzie szukać, nie wynik pomiaru.
Nie potwierdziłem w przeczytanych źródłach, jak zachowuje się <script>, gdy wprost dopuścisz go w tablicy. Sprawdź ten przypadek sam, nie zakładaj żadnego wyniku.
Dlaczego wp_kses() używa Tag Processora, a nie HTML Processora
HTML API ma dwie części. WP_HTML_Tag_Processor istnieje od WordPressa 6.2 i przechodzi przez dokument token po tokenie: znaczniki, komentarze i węzły tekstowe (dokumentacja klasy). WP_HTML_Processor pojawił się w 6.4, buduje drzewo dokumentu, a gdy trafia na znaczniki, których jeszcze nie obsługuje, przerywa wcześnie, żeby nie zepsuć dokumentu (dokumentacja klasy).
Według przypisu w raporcie nowe wp_kses() opiera się na Tag Processorze. Parsuje on każdy znacznik, komentarz i węzeł tekstowy, ale nie zna drzewiastej struktury dokumentu. HTML Processor buduje to drzewo, kosztem znacznie większej ilości czasu i pracy. Opis PR wyjaśnia, dlaczego otwarte elementy nie są zamykane: sporo kodu w rdzeniu wywołuje wp_kses() celowo z niepełnymi danymi, na przykład z otwierającym znacznikiem kontenera i częścią treści, osobno od znacznika zamykającego. Izolowanie treści przez zamykanie takich elementów psułoby strony. Dlatego niezbalansowane znaczniki z listy wyżej zostają niezbalansowane.
Czy moja własna tablica allowed html nadal działa
Większość tak, bo logika listy dozwolonych jest ta sama: znacznik lub atrybut jest w tablicy albo go nie ma. Zmienia się to, co wychodzi dookoła. Warto spojrzeć na trzy wzorce.
- Tablice dopuszczające inline
scriptlubstyle. Shortcode, który drukuje skrypt w treści i przepuszcza go przezwp_kses()z dopuszczonymscript, leży dokładnie w obszarze, którego dotyka raport. Porównaj stary i nowy wynik. Zastanów się też, czy skrypt nie powinien iść przezwp_add_inline_script(), a nie przez sanitizer. - Tablice dopuszczające
svglubmath. Typowy przypadek to zestawy ikon inline. Jeśli reguła z raportu ma zastosowanie, wynik może zostać ucięty na pierwszej konstrukcji, której parser nie obsłuży. Porównaj każdą ikonę, którą dostarczasz. - Tablice dla
<title>i podobnych.<title>to element specjalny, a jego zawartość jest traktowana jak tekst. Stąd przykład z escapowanym<sneaky>.
Jeśli tablica dopuszcza tylko a, strong, em, img i kilka atrybutów, skutek będzie raczej kosmetyczny: <IMG ... /> zamieni się w <img ...>.
Gdzie WordPress uruchamia wp_kses() bez Twojego wywołania
Rdzeń podpina oczyszczanie pod zapis. kses_init() jest podpięta pod hooki init i set_current_user w default-filters.php i dodaje filtry tylko wtedy, gdy bieżący użytkownik nie ma uprawnienia unfiltered_html (dokumentacja kses_init). Wtedy kses_init_filters() dodaje wp_filter_kses do title_save_pre, wp_filter_post_kses do content_save_pre, excerpt_save_pre i content_filtered_save_pre, a pre_comment_content filtruje wariantem podstawowym (dokumentacja kses_init_filters).
Strona dokumentacji wp_kses() wymienia też dziesiątki bezpośrednich wywołań w rdzeniu, w tym Walker_Comment::filter_comment_text(), wp_filter_oembed_result(), img_caption_shortcode(), filter_block_kses_value() oraz ekrany instalacji wtyczek i motywów (dokumentacja wp_kses). Przepisanie dotyka więc komentarzy, wyników oEmbed i podpisów obrazków, nawet jeśli Twój kod nigdy nie wywołuje wp_kses().
Dla testów ma to praktyczną konsekwencję. Na pojedynczej instalacji administratorzy i redaktorzy mają unfiltered_html, w multisite tylko superadministratorzy (role i uprawnienia). Ich treść nie przechodzi przez kses przy zapisie. Testuj zapis z konta z rolą Autor albo Współtwórca, bo tam filtry działają, a nie tylko ze swojego konta administratora.
Co psuje się w testach porównujących oczyszczony HTML
Wszystko, co sprawdza dokładny string. Test jak pierwsza asercja poniżej przechodzi dziś i może paść po zmianie, choć znaczniki znaczą to samo. Druga asercja normalizację przeżyje.
<?php
// Brittle: pins the exact string the sanitizer happens to emit.
$this->assertSame( '<a href="/x">Read</a>', wp_kses_post( "<A HREF='/x'>Read</A>" ) );
// Sturdier: asserts on what the markup means.
$p = new WP_HTML_Tag_Processor( wp_kses_post( "<A HREF='/x'>Read</A>" ) );
$this->assertTrue( $p->next_tag( 'a' ) );
$this->assertSame( '/x', $p->get_attribute( 'href' ) );Testuj zachowanie, nie bajty. Fixtury, które muszą zostać dosłowne, wygeneruj raz na trunku, przeczytaj diff własnymi oczami i zapisz nową wartość z komentarzem, od której wersji się zmieniła. Hurtowe “zaktualizuj snapshoty” to sposób, w jaki prawdziwa regresja dostaje akceptację.
To samo dotyczy testów wizualnych i plików wzorcowych. Szablony maili, które czyścisz przed wysyłką, to typowe miejsce, gdzie zapisany string porównuje się ze świeżym.
Jak używać assertEqualHTML() w testach wp_kses()
W komentarzach pod raportem Jon Surrell poleca assertEqualHTML() każdemu, komu testy padają przez różnice w kses. Metoda weszła w WordPressie 6.9 jako część WP_UnitTestCase (bilet 63527). Według wpisu JuanMy Garrido na Developer Blogu z 26 lutego 2026 parsuje oba stringi przez WP_HTML_Processor i porównuje drzewa, a nie bajty.
Pomija dokładnie ten rodzaj różnic, które przynosi przepisanie: wielkość liter w nazwach znaczników, styl cudzysłowów, kolejność atrybutów i klas, różne zapisy tego samego odwołania do znaku oraz powtórzone atrybuty, gdzie wygrywa pierwszy. Nadal pada, gdy zmienia się treść, wartość atrybutu albo klasa, lub gdy atrybutu brakuje albo jest nadmiarowy.
<?php
$this->assertEqualHTML(
'<a href="/x">Read</a>',
wp_kses_post( "<A HREF='/x'>Read</A>" )
);Ten sam wpis wyznacza granicę: gdy liczy się dokładna serializacja, na przykład zawartość <script> albo konkretna wartość data-wp-context, zostań przy assertSame(). Dla kses oznacza to assertSame() dla każdego dozwolonego przypadku script albo style i assertEqualHTML() dla reszty. Jeśli Twoja suita testów działa na WordPressie 6.9 lub nowszym, metoda jest już dostępna.
Jak testować na trunku bez dotykania produkcji
Trzy drogi, wszystkie na stagingu albo lokalnie. To moje propozycje, nie część oficjalnego raportu.
Wtyczka WordPress Beta Tester. Pozwala przenieść staging na nocne buildy, a później na bety 7.2. Według harmonogramu Beta 1 wychodzi 20 października 2026. Zmiana jest w trunku od 7 października, ale i tak sprawdź, czy w zainstalowanym kodzie istnieje filtr wp_kses_force_legacy_parser.
WP-CLI nightly. Na jednorazowej kopii:
# Staging only, never production.
wp core update --version=nightly --force
wp core version
wp eval-file bin/kses-diff.php > kses-diff.txt
tail -n 1 kses-diff.txtwp-env. Wskaż rdzeń na lustro trunka i zamontuj wtyczkę:
{
"core": "WordPress/WordPress#master",
"plugins": [ "." ]
}Którąkolwiek drogą pójdziesz, na koniec upewnij się, że filtr istnieje, na przykład przez grep -rn wp_kses_force_legacy_parser wp-includes/. Jeśli go nie ma, nie testujesz przepisania.
Jak porównać oczyszczony wynik na prawdziwej treści
Przypadki syntetyczne znajdą tylko błędy, które już sobie wyobraziłeś. Wartościowy test to Twoja własna baza. Skrypt poniżej czyści każdy opublikowany wpis dwa razy, raz z wymuszonym starym parserem i raz bez, i wypisuje tylko te wpisy, które się różnią.
<?php
// bin/kses-diff.php
// Run on a trunk build: wp eval-file bin/kses-diff.php > kses-diff.txt
$ids = get_posts( array(
'post_type' => 'any',
'post_status' => 'publish',
'numberposts' => -1,
'fields' => 'ids',
) );
$diff = 0;
foreach ( $ids as $id ) {
$raw = get_post_field( 'post_content', $id );
add_filter( 'wp_kses_force_legacy_parser', '__return_true' );
$old = wp_kses_post( $raw );
remove_filter( 'wp_kses_force_legacy_parser', '__return_true' );
$new = wp_kses_post( $raw );
if ( $old !== $new ) {
++$diff;
printf( "== %d %s\n--- old\n%s\n+++ new\n%s\n", $id, get_permalink( $id ), $old, $new );
}
}
printf( "%d of %d posts differ\n", $diff, count( $ids ) );Odpal go na kopii bazy produkcyjnej, bo redaktorzy wklejają dziwne rzeczy. Pierwsze dwadzieścia różnic przeczytaj ręcznie. Spodziewaj się kilku powtarzalnych przyczyn (samozamykające się znaczniki, luźny < przy kwocie, SVG inline), a naprawa jednej przyczyny załatwia wiele wpisów naraz.
Rozszerz to tym samym wzorcem: zamień wp_kses_post() na wywołanie, które robi Twoja wtyczka z własną tablicą, i podaj mu wartości opcji, tekst widgetów i komentarze, nie tylko wpisy.
Pamiętaj, co dokładnie mierzy skrypt. Wywołuje wp_kses_post() na całej treści, także na wpisach użytkowników z unfiltered_html, które przy zapisie nigdy nie przeszły przez kses. Dla takich wpisów diff nie opisuje zmiany widocznej dziś. Pokazuje, co by się stało, gdyby ktoś bez tego uprawnienia zapisał je ponownie.
Kiedy użyć filtra starego parsera
Filtr nazywa się wp_kses_force_legacy_parser. Zwrócenie true przywraca stary parser. Raport opisuje go jako sposób, w jaki właściciele witryn mogą wyłączyć nowy kod, a kluczowe jest słowo tymczasowy.
<?php
// wp-content/mu-plugins/kses-legacy-parser.php
// Stopgap only. Delete it once your output diff is clean.
add_filter( 'wp_kses_force_legacy_parser', '__return_true' );Są dwie sytuacje. Pierwsza: w oknie testów znalazłeś regresję na stronie klienta i musisz ustabilizować witrynę, zanim poprawisz kod albo zgłosisz błąd. Druga: wtyczka, której nie kontrolujesz, przestaje działać, a autor nie wydał poprawki. Wrzuć filtr do mu-pluginu, żeby był widoczny w jednym miejscu, i załóż zadanie z datą usunięcia.
Nie wdrażaj tego jako stałego ustawienia. Stara implementacja ma znane problemy, które przepisanie ma naprawić, między innymi awarie PCRE przy dużych wartościach atrybutów i zawartość skryptów pokazywaną jako tekst, według opisu PR. Ciche wyłączenie zachowuje te problemy i ukrywa migrację przed następnym programistą.
Raport nie obiecuje długiego okresu przejściowego. Mówi, że pierwotna implementacja zostaje na razie ze względów kompatybilności i zniknie w przyszłych wydaniach, może już w WordPressie 7.2.0. Planuj tak, jakby starego parsera mogło nie być w następnej głównej wersji.
Jak to działa w trunku: wp_kses() wywołuje filtr z domyślną wartością true, a default-filters.php rejestruje na nim __return_false i to właśnie włącza nowy kod. wp-settings.php ładuje mu-pluginy po default-filters.php, więc Twoje __return_true z domyślnym priorytetem uruchamia się później i wygrywa (kses.php w trunku). Nie wywołuj wp_sanitize_html_kses() bezpośrednio: docblock oznacza tę funkcję jako prywatną i przejściową.
Wydajność nowego wp_kses() w porównaniu ze starym parserem
Opublikowanych liczb nie ma. Zapytany o benchmarki w komentarzach pod raportem, Dennis Snell odpowiedział, że ta zmiana żadnych nie zawiera, a oczekiwanie jest takie, że nowy kod będzie szybszy i zużyje mniej pamięci. Podaje trzy powody:
- Stary kod dzieli dokument na dużą tablicę tokenów i trzyma ją w pamięci. Nowy idzie do przodu token po tokenie i tej tablicy nie przechowuje.
- Stary kod robi kilka przebiegów przetwarzania tekstu po całym dokumencie. Nowy usuwa większość z nich i w dużej mierze wykonuje jeden przebieg do przodu.
- Stary kod zawsze wywołuje
serialize_blocks( parse_blocks() )na całym dokumencie. Nowy parsuje blok dopiero wtedy, gdy rzeczywiście jakiś występuje, i opiera się naWP_Block_Parser, żeby odłożyć pracę jeszcze dalej.
Snell przyznaje też, że część przetwarzania HTML w nowej implementacji jest bardziej wymagająca. Jeśli Twoja strona czyści dużo treści przy każdym żądaniu, zmierz to na stagingu z włączonym i wyłączonym filtrem i wrzuć wyniki w komentarzach pod raportem. Właśnie o to prosi autor.
Jaki jest harmonogram WordPressa 7.2
Z posta o harmonogramie 7.2, wszystko o 15:00 UTC:
| Etap | Data |
|---|---|
| Beta 1 | 20 października 2026 |
| Beta 2 | 27 października 2026 |
| Beta 3 | 3 listopada 2026 |
| Beta 4 | 10 listopada 2026 |
| RC1 | 17 listopada 2026 |
| RC2 | 24 listopada 2026 |
| RC3 | 1 grudnia 2026 |
| Wydanie finalne | 8 grudnia 2026 |
Raport mówi: 7.2 albo 7.3. Nie możesz więc liczyć na żadne z wydań, ale nie możesz też wykluczyć 7.2. Rozsądny plan: skrypt różnic na stagingu przed Beta 1, ponownie po RC1, a wszystko zaskakujące zgłoś w bilecie Trac, póki okno jest otwarte. Autor prosi właśnie o takie opinie.
Co agencja powinna zrobić w tym tygodniu
- Przeszukaj kod pod kątem
wp_kses(,wp_kses_post(,wp_kses_data(i własnych tablic$allowed. Spisz, które dostają HTML od użytkowników lub redaktorów. - Postaw staging na trunku jedną z dróg powyżej i potwierdź, że filtr istnieje.
- Puść skrypt różnic na kopii treści produkcyjnej. Segreguj po przyczynie, nie po wpisie.
- Przepisz kruche testy tak, by sprawdzały strukturę, a fixtury tekstowe wygeneruj raz i zrecenzuj ręcznie.
- Sprawdź shortcodes i bloki, które emitują inline script, style, SVG lub MathML.
- Zdecyduj osobno dla każdego klienta, czy filtr jest potrzebny, opisz to i ustaw datę usunięcia.
Jeśli w projekcie leży kilka lat własnego czyszczenia HTML wokół ekosystemu wtyczek, taki audyt mieści się w naszej usłudze tworzenia i rozwijania stron WordPress na zamówienie.
Źródła
- Progress Report: wp_kses(), Make Core, 7 października 2026. Przykłady, nazwa filtra, zdanie o 7.2 lub 7.3.
- Harmonogram 7.2 release party, Make Core, 6 października 2026.
- Pull request 13271, KSES: Reimplement with Tag Processor. Nazwa funkcji, filtr, lista zmian zachowania. Bilety Trac 66208 i 65984 są tam wskazane.
- Bilet Trac 66208. Trac pokazał mi weryfikację przeglądarki, więc fakty o bilecie pochodzą z PR i z opisu commita.
- Changeset 64233 i commit 7119e1b na GitHubie, 7 października 2026. Data wejścia do trunka, opis commita, zamknięte bilety 37698, 58921, 59310 i 63208.
- kses.php w trunku i default-filters.php. Domyślna wartość filtra, adnotacja
@since 7.2.0, hookikses_init(). - Dokumentacja wp_kses(), kses_init(), kses_init_filters(), WP_HTML_Tag_Processor i WP_HTML_Processor na developer.wordpress.org.
- A better way to test HTML in WordPress with assertEqualHTML(), WordPress Developer Blog, 26 lutego 2026.
- Roles and Capabilities, dokumentacja WordPressa. Które role mają
unfiltered_html.
Sprawdzone 15 października 2026 na trunku. Zachowanie trunka może się zmienić przed wydaniem.







