Otwarte na nowe projekty +48 887 255 528 · kontakt[at]kosari.pl

Cannot redeclare is_woocommerce: jak kolejność wtyczek położyła sklep

Fatal error cannot redeclare is_woocommerce i panel bez wtyczek? Kolejność ładowania wtyczek, atrapa funkcji i watchdog. Diagnoza i naprawa krok po kroku.

9 min czytania WordPress
Cannot redeclare is_woocommerce: jak kolejność wtyczek położyła sklep

Klientka wchodzi do panelu WordPressa, a lista wtyczek jest prawie pusta. Sklep nie działa, kursanci nie mogą kupić dostępu. Ktoś próbuje włączyć WooCommerce – biała strona. Próbuje drugi raz – to samo. W logu błędów jedna linijka: PHP Fatal error: Cannot redeclare function is_woocommerce().

Błąd cannot redeclare is_woocommerce wygląda, jakby WooCommerce był zepsuty. Nie był. Do tej sytuacji doprowadziły trzy rzeczy naraz: alfabetyczna kolejność ładowania wtyczek, „zastępcza” funkcja zadeklarowana o ułamek sekundy za wcześnie i mechanizm, który miał pilnować, żeby jedna z wtyczek zawsze była włączona. Jedną z tych rzeczy była wtyczka narzędziowa, którą sami utrzymujemy – piszemy o tym uczciwie, bo to najlepsza lekcja z całej historii.

Kontekst: platforma kursowa, która stanęła na kilkanaście godzin

Klientka prowadzi platformę kursową na WordPressie: sklep na WooCommerce, kilkaset kont kursantów, dostęp do kursów nadawany po zakupie. Każda godzina przestoju to zablokowana sprzedaż i kursanci, którzy piszą, że nie mogą się zalogować do materiałów.

Sklep padł późnym wieczorem i nie działał przez kilkanaście godzin. Współpracownik klientki w końcu zmienił przez FTP nazwy katalogów naszych wtyczek, co wyłączyło je siłą – i sklep ruszył. Zdążył jeszcze założyć zgłoszenie u firmy hostingowej z tezą, że winny jest serwer. Następnego dnia pod wieczór sprawa trafiła do nas.

Diagnoza: linia czasu z logów, co do sekundy

Ślepa uliczka: „wstrzymane wtyczki” i błędy cronów

Pierwsze, co rzucało się w oczy w logach, to wpisy o wstrzymanych wtyczkach i dziesiątki ostrzeżeń invalid_schedule przy zadaniach cyklicznych. Wyglądało to na przyczynę. Okazało się skutkiem: WordPress wstrzymuje wtyczkę, która wywołała błąd krytyczny, a zadania cykliczne wyłączonej wtyczki zostają w bazie z harmonogramem, którego już nikt nie rejestruje. To był hałas po awarii, a nie jej źródło.

Krok 1: kto wyłączył wszystkie wtyczki?

Nikt nie wchodził na stronę wtyczek w panelu. Lista aktywnych wtyczek skurczyła się sama. Klucz był w mechanizmie, który nasza wtyczka instalowała jako mu-plugin, czyli „wtyczkę obowiązkową” ładowaną przed wszystkimi innymi. Jego zadaniem było pilnować, żeby nasza wtyczka zawsze była aktywna. Robił to tak:

$active = get_option('active_plugins');
$active[] = $nasza_wtyczka;
update_option('active_plugins', $active);

Wygląda niewinnie: odczytaj listę, dopisz siebie, zapisz. Problem w tym, że get_option() zwraca wartość po przejściu przez filtry. Na tej stronie działała wtyczka do kopii testowych (WP Staging), która przy niektórych zapytaniach AJAX celowo podaje skróconą listę aktywnych wtyczek, żeby jej własne operacje szły szybciej. To jej prawo – lista w bazie się nie zmienia. Ale nasz watchdog odczytał tę skróconą listę i zapisał ją do bazy na stałe. Wszystkie pozostałe wtyczki strony zostały wyłączone jednym zapisem.

Krok 2: dlaczego WooCommerce nie dawał się włączyć?

Tu wchodzi główny bohater. Nasza wtyczka miała moduły, które korzystają z funkcji WooCommerce, takich jak is_cart() czy is_woocommerce(). Żeby nie wywracać strony, gdy WooCommerce jest wyłączony, deklarowała ich zastępcze wersje (polyfille) – ale tylko wtedy, gdy WooCommerce nie był na liście aktywnych wtyczek.

WordPress ładuje aktywne wtyczki w kolejności alfabetycznej według nazwy katalogu. Nasza wtyczka startuje przed katalogiem woocommerce. Gdy WooCommerce wypadł z listy aktywnych, nasza wtyczka przy każdym ładowaniu strony deklarowała is_woocommerce(). Moment później włączany ręcznie WooCommerce próbował zadeklarować tę samą funkcję – a w PHP funkcji o tej samej nazwie nie da się zadeklarować dwa razy.

Dlaczego kolejność ładowania wtyczek blokuje WooCommerce1. mu-pluginswatchdog2. inne wtyczkialfabetycznie3. wtyczka naszadeklaruje is_woocommerce()4. woocommerceCannot redeclareFunkcja już istnieje, więc WooCommerce kończy się błędem krytycznym.Następne ładowanie: WooCommerce znów nieaktywny, więc atrapa znów powstaje.
Stan „WooCommerce nieaktywny” sam siebie podtrzymywał: atrapa funkcji blokowała jedyną drogę wyjścia z niego.

Atrapa funkcji cudzej wtyczki zamienia stan odwracalny w nieodwracalny. Wyłączony WooCommerce to drobiazg – wystarczy go włączyć. Wyłączony WooCommerce plus zadeklarowana wcześniej is_woocommerce() to sklep, którego nie da się włączyć z panelu w ogóle.

Krok 3: dlaczego nie dało się wyłączyć naszej wtyczki z panelu?

Naturalny ruch to wyłączyć wtyczkę, która deklaruje atrapę. W logach widać, co się wtedy działo: wtyczka była wyłączana, a przy następnym zapytaniu watchdog włączał ją z powrotem. Po ponownej aktywacji, kilka sekund później – kolejny błąd krytyczny WooCommerce. Z perspektywy człowieka panel „nie reagował”, a jedyną skuteczną drogą zostało FTP.

Trzy mechanizmy, które razem zablokowały sklepCudzy filtr skracalistę wtyczek w locieWatchdog zapisujeskróconą listę do bazyWtyczki stronywyłączone, w tym sklepAtrapa is_woocommerce()blokuje włączenie sklepuWyłączenie naszej wtyczkicofa watchdogZostaje tylko FTPi kilkanaście godzin przestojuŻaden z trzech mechanizmów osobno nie położyłby sklepu na tak długo.
„Wtyczka X położyła sklep” w praktyce prawie zawsze znaczy: kilka wtyczek uzgadnia ten sam stan w złej kolejności.

Jak czytać błąd Cannot redeclare

Pełny komunikat wygląda mniej więcej tak (ścieżki skrócone):

PHP Fatal error: Cannot redeclare function is_woocommerce()
(previously declared in /wp-content/plugins/INNA-WTYCZKA/plik.php:161)
in /wp-content/plugins/woocommerce/includes/wc-conditional-functions.php on line 28

Najważniejsza część to „previously declared in”. Wskazuje wtyczkę, która zadeklarowała funkcję jako pierwsza – to ona jest podejrzana, a nie plik, w którym błąd się ujawnił. WooCommerce deklaruje swoje funkcje warunkowe bez sprawdzania, czy już istnieją, bo ma do tego pełne prawo: to są jego funkcje.

Ten sam błąd pojawia się też w innym, banalnym scenariuszu: gdy w katalogu plugins leżą dwie kopie tej samej wtyczki, np. zostawiony folder z kopią zapasową. WordPress widzi wtedy dwa pliki z nagłówkiem wtyczki i przy aktywacji obu dostaje te same funkcje dwa razy. Kopie zapasowe wtyczek trzymaj poza katalogiem plugins.

Jeśli nie chcesz sam przekopywać logów o północy, gdy sklep stoi, po to jest stała opieka WordPress – z monitoringiem, który zauważa błąd krytyczny, zanim zadzwoni klientka.

Rozwiązanie: co zmieniliśmy w kodzie

1. Atrapy cudzych funkcji tylko wtedy, gdy wtyczki nie ma na dysku

Zainstalowana, ale wyłączona wtyczka to stan, z którego człowiek wychodzi jednym kliknięciem. Atrapa ma prawo istnieć wyłącznie wtedy, gdy tej wtyczki w ogóle nie ma:

$woo_na_dysku = file_exists(WP_PLUGIN_DIR . '/woocommerce/woocommerce.php');
if (!$woo_na_dysku && !function_exists('is_woocommerce')) {
    function is_woocommerce() { return false; }
}

Jeszcze lepiej w ogóle nie deklarować niczego na poziomie pliku i sprawdzać obecność WooCommerce wewnątrz własnych funkcji albo podpinać moduł sklepowy dopiero pod akcję, którą WooCommerce sam wywołuje po załadowaniu:

add_action('woocommerce_loaded', 'moj_modul_sklepu_start');

function moj_rabat_w_koszyku() {
    if (!function_exists('WC') || !WC()->cart) {
        return; // brak sklepu = moduł po prostu nic nie robi
    }
    // ...
}

2. Watchdog, który nie zapisuje cudzej listy wtyczek

Nowa wersja mechanizmu pilnującego czyta listę aktywnych wtyczek bezpośrednio z bazy, z pominięciem filtrów, więc cudza wtyczka nie podsunie mu skróconej wersji. Do tego ma walidator: nowa lista musi być starą listą plus dokładnie jeden wpis, nigdy krótsza. Poprzednia lista trafia do kopii zapasowej, liczba prób jest ograniczona do trzech na dobę, każda próba trafia do dziennika, a człowiek ma wyłącznik (stała, opcja albo pusty plik na serwerze). Watchdog nie rusza też wtyczek, które WordPress sam wstrzymał po błędzie krytycznym.

// Nigdy tak:
update_option('active_plugins', $lista_z_get_option);

// Zamiast tego:
activate_plugin($nasza_wtyczka); // i tylko po walidacji, z limitem prób

3. Sprzątanie po sobie

Dopisaliśmy też zasadę: przy wyłączaniu wtyczki usuwamy jej zadania cykliczne, żeby po deaktywacji nie zostawiała w logu ostrzeżeń, które deweloper strony bierze potem za przyczynę awarii.

Przywrócenie sklepu

Wtyczki włączaliśmy w kontrolowanej kolejności, sprawdzając stronę po każdym kroku. Na koniec weryfikacja tego, co dla klientki było najważniejsze: dostęp kursantów. Wszystkie 437 kont i 428 zapisów na kursy było na miejscu, nikt nie został bez dostępu. A serwer, któremu przypisano winę w zgłoszeniu do hostingu, był w tej historii niewinny.

Efekt

Sklep działa, a poprawiona wersja trafia na kolejne strony z naszą wtyczką – przy każdej sprawdzamy, którą wersję mechanizmu pilnującego ma zainstalowaną. Na jednej z nich zrobiliśmy test celowo prowokujący ten sam scenariusz – wielokrotne wywołanie trybu, w którym inna wtyczka podmienia listę aktywnych w locie. Liczba aktywnych wtyczek przed i po była taka sama, a dziennik watchdoga pusty.

Jak sprawdzić u siebie

  • Przy błędzie „Cannot redeclare” czytaj fragment previously declared in – to tam jest winowajca.
  • Sprawdź kolejność ładowania: wp option get active_plugins (WP-CLI) pokazuje listę w kolejności, w jakiej WordPress ładuje wtyczki. Mu-pluginy z wp-content/mu-plugins idą przed wszystkimi.
  • Przejrzyj mu-pluginy. Jeśli któryś zapisuje active_plugins, to sygnał alarmowy.
  • Poszukaj wtyczek, które filtrują listę aktywnych w locie (option_active_plugins, pre_option_active_plugins) – robią to m.in. narzędzia do kopii testowych i tryby awaryjne kreatorów stron.
  • Sprawdź, czy w katalogu plugins nie leżą kopie zapasowe wtyczek.
  • Upewnij się, że mail administratora w ustawieniach WordPressa jest prawdziwy – na niego przychodzi link trybu odzyskiwania po błędzie krytycznym.

Czego nauczyła nas ta historia

Nie deklarujemy funkcji cudzych wtyczek na poziomie pliku i nie zapisujemy listy aktywnych wtyczek ręcznie. Każdy mechanizm, który „sam pilnuje porządku” na stronie klienta, musi mieć wyłącznik dla człowieka i limit prób – inaczej w złym dniu walczy z osobą, która próbuje ratować sklep. I przede wszystkim: gdy strona pada, szukamy kolejności zdarzeń, a nie jednej winnej wtyczki.

FAQ

Co oznacza błąd Cannot redeclare is_woocommerce?

Że funkcja is_woocommerce() została już zadeklarowana przez inny kod, zanim WooCommerce zdążył zadeklarować własną. PHP nie pozwala na dwie funkcje o tej samej nazwie, więc ładowanie WooCommerce kończy się błędem krytycznym. Winowajcę wskazuje część komunikatu „previously declared in”.

W jakiej kolejności WordPress ładuje wtyczki?

Najpierw wtyczki obowiązkowe z mu-plugins, potem aktywne wtyczki w kolejności zapisanej w opcji active_plugins, która przy aktywacji jest sortowana alfabetycznie według katalogu i nazwy pliku. Dlatego wtyczka o nazwie zaczynającej się na „a” startuje przed WooCommerce.

Jak odzyskać panel WordPressa po błędzie krytycznym bez FTP?

Od wersji 5.2 WordPress wysyła na adres administratora mail z linkiem trybu odzyskiwania, w którym można wyłączyć winną wtyczkę. Pomaga też WP-CLI (wp plugin deactivate nazwa --skip-plugins) albo menedżer plików w panelu hostingu. Jeśli wtyczka sama się włącza z powrotem, najpierw wyłącz mu-plugin, który ją pilnuje.

Czy mogę po prostu usunąć WooCommerce i zainstalować go od nowa?

Nie pomoże, jeśli funkcję deklaruje inna wtyczka – nowy WooCommerce trafi na ten sam konflikt. Najpierw trzeba usunąć przyczynę z „previously declared in”. Dane sklepu leżą w bazie, więc sama reinstalacja ich nie kasuje, ale to i tak operacja, przed którą robi się kopię zapasową.

Dlaczego wtyczka sama włącza się z powrotem po wyłączeniu?

Zwykle stoi za tym mu-plugin albo moduł „pilnujący”, który przy każdym ładowaniu strony aktywuje wtyczkę ponownie. Takiego mechanizmu nie wyłączysz z listy wtyczek – trzeba go znaleźć w wp-content/mu-plugins i wyłączyć tam albo użyć jego wyłącznika, jeśli go ma.

Twój sklep albo platforma kursowa stanęła po aktualizacji lub włączeniu wtyczki i nie wiesz, od czego zacząć?

Przywracamy strony po błędach krytycznych, czytamy logi co do sekundy i zostawiamy po sobie zabezpieczenia, żeby ta sama awaria nie wróciła. Jeśli budujesz platformę z kursami od zera, zajrzyj też na platformykursowe.pl.

Napisz do nas – opisz, co widzisz w panelu, a pomożemy podnieść sklep

Grzegorz Rówiński · Kosari

Buduję strony, sklepy i platformy kursowe, automatyzuję procesy i wdrażam AI w firmach. Ten wpis powstał z realnego wdrożenia, nie z teorii.

Napisz do mnie →

Przeczytaj też

Masz podobny problem i nie chcesz go debugować sam?

Napisz krótko co się dzieje. Odpowiadamy konkretnie, bez zobowiązań - powiemy z czego to najpewniej wynika i ile kosztuje naprawa.

Napisz do nas

Wolisz porozmawiać? Zadzwoń: +48 887 255 528

Zadzwoń +48 887 255 528