Przejdź do treści
Konfiguracja

Search and replace adresu URL w bazie WordPressa bez niszczenia danych serializowanych

Zmień adres strony WordPress w bazie danych, nie kasując przy tym widgetów, menu ani ustawień szablonu. Poznaj polecenie WP-CLI, które nie psuje danych serializowanych.

Opublikowano

Nie uruchamiaj zwykłego UPDATE ... SET column = REPLACE(column, 'oldurl', 'newurl') na bazie WordPressa. Tekst rzeczywiście się podmieni, ale przy okazji zniszczysz sobie ustawienia — WordPress przechowuje tablice i obiekty jako łańcuchy serializowane przez PHP, a ten format zapisuje długość każdego tekstu w bajtach. Surowa podmiana w SQL przepisuje tekst, ale nie rusza zapisanej długości, i wartość przestaje nadawać się do odczytu.

Użyj narzędzia, które rozumie serializację. W wierszu poleceń jest to wp search-replace. Najpierw kopia zapasowa bazy, potem przebieg próbny, a dopiero na końcu uruchomienie na serio.

Co się właściwie psuje

Łańcuch serializowany przez PHP wygląda tak:

s:18:"http://example.com"

18 to długość następującego tekstu w bajtach. Policz — http://example.com ma dokładnie 18 znaków.

Teraz wyobraź sobie zapisane ustawienie szablonu:

a:1:{s:4:"logo";s:18:"http://example.com";}

Zrób zwykłą podmianę SQL z http:// na https:// i powinieneś dostać:

a:1:{s:4:"logo";s:19:"https://example.com";}

Tyle że nie dostaniesz. Dostaniesz to:

a:1:{s:4:"logo";s:18:"https://example.com";}

Łańcuch ma teraz 19 bajtów, ale wciąż twierdzi, że ma 18. PHP odczytuje zadeklarowaną długość, przechodzi 18 bajtów do przodu, nie znajduje tam cudzysłowu i średnika, i się poddaje. Cała tablica nie daje się odczytać.

Dlaczego błąd przechodzi bez śladu

To właśnie ta część czyni ten błąd tak kosztownym. WordPress nie wyrzuca komunikatu i nie zatrzymuje się. Przy odczycie opcji przepuszcza wartość przez swoją funkcję pomocniczą, PHP zwraca false dla uszkodzonych danych, a WordPress podaje Twojemu kodowi wartość, która zachowuje się tak, jakby opcja nigdy nie została ustawiona. Wtyczka albo szablon wraca wtedy do wartości domyślnej.

Objawem nie jest więc strona błędu. Objawem jest:

  • Widgety znikają z paneli bocznych
  • Ustawienia personalizacji wracają do domyślnych wartości szablonu
  • Dane układu page buildera wracają puste i strona renderuje się pusta
  • Ekrany ustawień wtyczek wyglądają jak po świeżej instalacji
  • Menu tracą przypisane lokalizacje

Nic w kokpicie nie sygnalizuje problemu. Wiersz nadal jest w bazie, nadal pełen tekstu, który wygląda poprawnie. Tylko prefiks długości nie zgadza się o bajt czy dwa. PHP faktycznie zgłasza komunikat, gdy deserializacja się nie powiedzie, ale na produkcji z wyłączonym wyświetlaniem błędów nikt tego nie zobaczy. Włączenie WP_DEBUG razem z WP_DEBUG_LOG i WP_DEBUG_DISPLAY ustawionym na false zapisze te komunikaty do pliku dziennika, gdzie da się je przeczytać.

Szkody nie rozkładają się też równomiernie. Proste wartości skalarne — siteurl, home, zwykła treść wpisu, adres siedzący we własnej kolumnie — przechodzą naiwną podmianę bez szwanku. Psują się wyłącznie wartości serializowane. Dlatego zła podmiana na pierwszy rzut oka często wygląda na udaną.

Sprawdź wp-config.php, zanim czegokolwiek dotkniesz

Jeśli wp-config.php definiuje WP_HOME albo WP_SITEURL, te stałe wygrywają z tym, co jest w bazie. Zmiana wp_options będzie wtedy wyglądała na kompletnie bezskuteczną. Poszukaj takich linii, zanim uznasz, że problem leży w bazie:

define( 'WP_HOME', 'http://example.com' );
define( 'WP_SITEURL', 'http://example.com' );

Albo zaktualizuj je do nowego adresu, albo usuń i pozwól decydować bazie.

Najpierw kopia zapasowa — i to na serio

Uszkodzonej serializacji nie zawsze da się łatwo cofnąć. Gdy prefiks długości i treść przestaną się zgadzać, oryginalnej wartości nie odzyskasz z uszkodzonej — nie da się ustalić, jaka długość była poprawna, nie znając pierwotnego tekstu. Kopia zapasowa to jedyne prawdziwe cofnięcie.

wp db export backup-before-replace.sql

Albo przez mysqldump, jeśli WP-CLI nie jest dostępne:

mysqldump -u USER -p DBNAME > backup-before-replace.sql

Jeśli możesz, trzymaj ją poza serwerem. Kopia leżąca w katalogu głównym witryny jest jednocześnie punktem przywracania i dziurą w bezpieczeństwie.

Bezpieczne polecenie

Polecenie search-replace z WP-CLI rozpakowuje każdą wartość, przechodzi po odkodowanej strukturze, podmienia tekst w środku i pakuje ją z powrotem z poprawnymi długościami. Najpierw przebieg próbny:

wp search-replace 'http://example.com' 'https://example.com' --dry-run --all-tables --skip-columns=guid

Przeczytaj raport. Pokazuje, ile podmian nastąpi w każdej tabeli i kolumnie. Jeśli lista tabel wygląda podejrzanie albo tabela, której nie rozpoznajesz, ma tysiące trafień, zatrzymaj się i sprawdź to przed prawdziwym uruchomieniem.

Potem uruchom właściwe polecenie:

wp search-replace 'http://example.com' 'https://example.com' --all-tables --skip-columns=guid --report-changed-only

Uczciwie o tych flagach:

  • --all-tables obejmuje tabele, których WordPress sam nie rejestruje, a to właśnie tam często siedzą dane wtyczek. Bez tej flagi część tabel wtyczek zostanie całkowicie pominięta.
  • --skip-columns=guid ma znaczenie. Pole guid to stały identyfikator dla czytników kanałów, a nie żywy adres. Jego przepisanie może sprawić, że subskrybenci zobaczą całe Twoje archiwum jako nowe wpisy.
  • Szukaj bez końcowego ukośnika i bez wariantów protokołu, których nie chcesz ruszać. Podmiana samego example.com trafi także w adresy e-mail i każdą wzmiankę w treści wpisów.

Jeśli nie możesz skorzystać z wiersza poleceń, wtyczki migracyjne rozumiejące serializację wykonają tę samą pracę z poziomu kokpitu. Szukasz zachowania opisanego wprost jako obsługa danych serializowanych; jeśli narzędzie tego nie deklaruje, załóż, że tego nie robi.

Których tabel i kolumn to dotyczy

TabelaKolumnaRyzyko serializacji
wp_optionsoption_valueWysokie — widgety, ustawienia szablonu, transienty, większość opcji wtyczek
wp_postmetameta_valueWysokie — układy page builderów, tablice pól własnych
wp_usermetameta_valueWysokie — uprawnienia, ustawienia ekranów i kokpitu
wp_termmetameta_valueŚrednie — zależy od wtyczek
wp_postspost_content, guidNiskie dla treści, ale atrybuty shortcode’ów i znaczniki bloków niosą adresy URL
wp_commentscomment_content, comment_author_urlNiskie
wp_linkslink_url, link_imageNiskie, a do tego zwykle nieużywane

W trybie multisite dochodzą wp_blogs, wp_site, wp_sitemeta oraz tabele poszczególnych witryn: wp_2_options, wp_3_options i tak dalej. Zmiana adresów w multisite rządzi się dodatkowymi zasadami wykraczającymi poza zwykłą podmianę — potraktuj to jako osobne zadanie.

Przypadki, których search-replace nie załatwia do końca

Uczciwie o ograniczeniach:

JSON zapisany w bazie. Część page builderów przechowuje układ jako JSON z ukośnikami poprzedzonymi znakiem ucieczki, więc adres wygląda tak: https:\/\/example.com, a nie https://example.com. Wyszukiwanie normalnej postaci go przeoczy. Może być potrzebny drugi przebieg na wersji z ucieczkami. Przetestuj to na kopii.

Wartości zakodowane w Base64. Jeśli wtyczka koduje ustawienia przed zapisem, żadna podmiana tekstowa nie zobaczy adresu. Takie ustawienia zwykle trzeba wpisać ponownie na własnym ekranie wtyczki.

Adresy wpisane na sztywno w plikach. Wszystko, co trafiło do functions.php, szablonu motywu potomnego albo do zapisanej na sztywno ścieżki zasobu, nie jest w bazie i nie zostanie ruszone. Przeszukaj katalog szablonu osobno.

Bufory i wygenerowane wyniki. Cache obiektów, cache stron i kopie w CDN nadal serwują stare adresy po poprawnej podmianie. Wyczyść je i sprawdź jeszcze raz, zanim uznasz, że podmiana się nie udała.

Jeśli już uruchomiłeś złe zapytanie

Przywróć kopię zapasową. To jest odpowiedź i warto powiedzieć to wprost, zamiast proponować procedurę naprawczą, która w większości przypadków nie działa.

Jeśli kopii nie ma, pole manewru jest wąskie: wartości, które nigdy nie były serializowane, są w porządku, a uszkodzone serializowane trzeba odtworzyć ręcznie albo zresetować, zapisując ponownie odpowiedni ekran ustawień. Ponowny zapis obszaru widgetów, ponowne wskazanie lokalizacji menu czy ponowne wpisanie ustawień wtyczki nadpisze uszkodzoną wartość świeżą i poprawnie zserializowaną. Żmudne, ale skuteczne — i lepsze niż zostawienie uszkodzonych wierszy, które wprowadzą w błąd następną osobę zaglądającą do tej strony.

Żeby wygenerować poprawne zapytania przed uruchomieniem czegokolwiek, zbuduj je w narzędziu do search and replace w WordPressie — pokazuje, które kolumny można bezpiecznie ruszyć zwykłym SQL-em, a które wymagają obsługi rozumiejącej serializację, więc widzisz ten podział, zanim zdecydujesz się na konkretne polecenie.

FAQ

Pytania

Dlaczego zwykły REPLACE w SQL psuje stronę na WordPressie?

Bo WordPress zapisuje tablice i obiekty jako tekst serializowany przez PHP, a ten format zapamiętuje długość każdego łańcucha znaków w bajtach. Surowy REPLACE zmienia tekst, ale zostawia starą liczbę bajtów. PHP odmawia wtedy odczytania wartości, WordPress podstawia domyślną, a ustawienia wyglądają na puste zamiast na błędne.

Jak najbezpieczniej zmienić adres URL strony WordPress w bazie danych?

Zrób pełną kopię zapasową bazy, a potem uruchom polecenie WP-CLI wp search-replace, najpierw w trybie próbnym. WP-CLI rozpakowuje każdą wartość, podmienia tekst wewnątrz odkodowanej struktury i pakuje ją z powrotem z poprawionymi długościami. Wtyczki migracyjne, które rozumieją serializację, robią to samo z poziomu kokpitu, jeśli nie masz dostępu do wiersza poleceń.

W których tabelach siedzi stary adres URL?

Głównie w wp_options, wp_posts, wp_postmeta, wp_usermeta, wp_termmeta i wp_comments. Ryzyko serializacji dotyczy przede wszystkim wp_options oraz każdej kolumny meta_value, bo widgety, ustawienia szablonu, transienty i dane page builderów są zapisywane jako tablice. W trybie multisite domenę noszą też wp_blogs, wp_site i wp_sitemeta.

Czy zmiana w bazie zadziała, jeśli adres jest zdefiniowany w wp-config.php?

Nie. Jeśli wp-config.php definiuje WP_HOME albo WP_SITEURL, te stałe mają pierwszeństwo przed wartościami siteurl i home z wp_options. Edycja bazy nie zmieni wtedy niczego widocznego, dopóki nie poprawisz albo nie usuniesz stałych. Sprawdź wp-config.php jako pierwszy plik, bo ta jedna linijka tłumaczy sporo nieudanych migracji.

Czy podmieniać także kolumnę guid?

Nie. Pole guid to stały identyfikator dla czytników kanałów, a nie działający adres. Jego przepisanie może sprawić, że subskrybenci zobaczą wszystkie stare wpisy jako nowe. Przy uruchamianiu WP-CLI dodaj skip-columns z guid. Jedyny częsty wyjątek to strona, która nigdy nie miała publicznych kanałów ani subskrybentów.

Po czym poznać, że wartość została uszkodzona przez złą podmianę?

Uszkodzone wartości serializowane wyglądają w bazie normalnie, ale wracają do WordPressa puste. Widgety znikają, ustawienia personalizacji się resetują, a opcje wtyczek wracają do domyślnych bez żadnego komunikatu. Włączenie WP_DEBUG i WP_DEBUG_LOG wyświetli komunikat o nieudanej deserializacji. Porównanie zadeklarowanej długości z rzeczywistą liczbą bajtów potwierdza to wprost.