WordPress - motywy, wtyczki, informacje, programowanie

wp_filters

Haki WordPressa cz.2 – Akcje i filtry od środka

Z drugiej części poradnika na temat haków WordPressa (hooks) dowiesz sie więcej o akcjach i filtrach. Pokażemy inne metody ich dodawania (z uwzględnieniem funkcji anonimowych i klas). Podglądniemy też jak WordPress nimi zarządza i jakich struktur do tego używa. Pokażemy jak śledzić wykonanie akcji, w jaki sposób stwierdzić, które i w jakiej kolejności się wykonują.

Przeczytaj najpierw pierwszą część poradnika: Haki WordPressa cz. 1 – Wprowadzenie do akcji i filtrów

Nazewnictwo akcji i filtrów

Bardzo istotną rzeczą, o której musimy pamiętać przy dodawaniu akcji i filtrów w WordPressie jest to, że nazwa naszej funkcji może skolidować z tymi, które już istnieją, lub zostaną dodane z jakąś wtyczką. Dotyczy to zresztą nie tylko akcji i filtrów, ale każdej innej funkcji jaką dodamy do serwisu.

Aby tego uniknąć, powinniśmy zawsze starać się poprzedzić nazwę funkcji możliwie charakterystycznym, kilkuliterowym przedrostkiem. Możemy w tym celu wykorzystać inicjały, albo skrót od nazwy firmy, albo jeśli funkcje są częścią jakiejś wtyczki, skrót od jej nazwy.

Serwis WPinternals korzysta z omawianego w poprzedniej części poradnika haka excerpt_length, ale nie podstawia do niego funkcji o nazwie na przykład set_excerpt_length tylko

add_filter( 'excerpt_length', 'wpint_set_excerpt_length' );

Przedrostek wpint_ bierze się od nazwy WPinternals. Co prawda nie ma gwarancji, że nigdy z niczym nie skoliduje, ale prawdopodobieństwo zderzenia się z istniejącą funkcja set_excerpt_length jest znacznie większe. W niektórych publikacjach zalecane jest horrendalne przedrostkowanie przez połączenie nazwy autora i nazwy wtyczki/modułu, ale jak w wielu innych sprawach jestem tu za unikaniem przesady. Zdrowy rozsądek przede wszystkim.

Funkcje anonimowe

W niektórych wypadkach możemy zdecydować, że wystarczy posłużyć się funkcją anonimową, bo działanie filtra sprowadza się na przykład do zwrócenia jednej liczby. Znowu posługując się przykładem filtra excerpt_length moglibyśmy napisać

add_filter( 'excerpt_length', function () { return 5; } );

Funkcja zwracająca liczbę 5 nie ma nazwy i nie da się jej wywołać normalnie, ale można ją podać jako parametr lub przypisać do zmiennej. To funkcja anonimowa – closure. Może ona też mieć parametry:

add_filter('the_title', function($title) { return 'Tytuł: '. $title;}) ;

Takie funkcje działają od PHP 5.3 wzwyż. Jeśli Twój WordPress działa na wcześniejszej wersji PHP, możesz utworzyć funkcję anonimową przy pomocy create_function:

add_filter( 'excerpt_length', create_function ( '', 'return 5;' ) );

Pamiętaj jednak, że w przeciwieństwie do closures, utworzone w ten sposób funkcje nie mogą być zcachowane przez żaden optymalizator. Jest to więc w niektórych sytuacjach rozwiązanie mniej wydajne.

Trzeba też pamiętać, że anonimowych akcji i filtrów nie możemy usunąć przez remove_action czy remove_filter ponieważ w obydwu przypadkach wymagane jest podanie nazwy funkcji. Należy przyjąć, że powinniśmy ograniczyć użycie tego rozwiązania tylko do wyjątkowo prostych sytuacjach.

Funkcje standardowe

Dla najprostszych przypadków, kiedy chcemy aby nasz filtr zwrócił po prostu true, false, zero, albo pusta tablice, mamy zdefiniowane w pliku /wp-includes/functions.php i dostępne w każdej chwili cztery funkcje:

__return_true();

__return_false();

__return_zero();

__return_empty_array();

Akcje i filtry w klasach

Załóżmy, że do działania naszego serwisu potrzebujemy jakichś struktur danych, które dla porządku zamknęliśmy w klasę MyStructures. Klasa ta ma statyczną metodę init_all(), która inicjuje wszystkie te struktury (na przykład wczytuje dane z bazy, albo z plików.

class MyStructures
{
    public static function init_all () {
      // inicjalizacja bardzo ważnych struktur
    }
};

Możemy teraz użyć metody init_all jako akcji, która będzie wywołana przy inicjalizacji WordPressa (na haku 'init') w następujący sposób:

add_action('init', array('MyStructures', 'init_all'));

Jest to bardzo dobra (preferowana) metoda pozwalająca na uniknięcie kolizji nazw, ponieważ metoda init_all nie skoliduje z żadną globalną funkcja ani metodą innej klasy o tej nazwie. Zamiast stosować przedrostki możemy więc nasze akcje i filtry mieć elegancko zgrupowane w jakiejś klasie.

Jeśli metoda, którę chcemy wywołać nie jest statyczna, dysponujemy instancją klasy. Na przykład wyobraźmy sobie, że mamy klasę Dictionary, która potrafi wczytać dowolny słownik i jest wyposażona w metodę translate, którą możemy użyć jako filtr tłumaczący zawartość wpisów:

class Dictionary
{
    public function translate () {
        // tłumaczy na zadany język
    }
};

$dictEnglish = new Dictionary('english');
add_filter('the_content', array($dictEnglish, 'translate'));

Słownik jest najpierw inicjowany z parametrem 'english' (co na przykład powoduje wczytanie słownika z dysku), powstaje więc instancja tej klasy (obiekt). Następnie metoda translate tej konkretnej instancji słownika, a więc słownika angelskiego, jest podpięta jako filtr, który tłumaczy zawartość artykułów w serwisie.

Jak WordPress zarządza filtrami i akcjami

Oprócz zestawu funkcji, które omówiliśmy dokładnie w pierwszej części poradnika, możemy zaglądać do zmiennych globalnych, w których WordPress przechowuje wszelkie informacje o systemie haków.

$wp_filter to tablica wszystkich akcji i filtrów dodanych przez add_action i add_filter. Każdy filter i akcja są zapamiętane po nazwie, wraz obsługująca je funkcja, deklarowaną liczba jej parametrów i priorytetem.

$wp_current_filter to zmienna, w której znajdziemy nazwę filtra lub akcji, która jest w danej chwili wykonywana. W ten sposób w każdej funkcji lub metodzie możemy zawsze sprawdzić, czy została wywołana z wnętrza jakiegoś filtra lub akcji.

$wp_actions to tablica, w której WordPress zlicza wszystkie wywołania akcji (nie filtrów). Dzięki temu możemy zawsze przy pomocy funkcji did_action sprawdzić czy dana akcja była wykonana i ile razy. Kierując się tą informacją, przynajmniej teoretycznie możemy stwierdzić w jakiej fazie działania WordPressa jesteśmy. Najczęściej jednak używa się tej informacji po to, żeby daną czynność w akcji wywołać tylko raz bez konieczności powoływania własnych zmiennych globalnych.

$merged_filters to tablica, która raczej nigdy nam się nie przyda, ale warto wiedzieć, że jest. WordPress zapamiętuje w niej fakt posortowania filtrów dla danego haka. Kiedy nowy filtr jest dodawany lub tablica jest zmieniana w inny sposób, filtry dla danego haka są sortowane funkcją ksort po priorytecie. Żeby nie powtarzać tego niepotrzebnie, WordPress pamięta, które filtry już sortował.

Podglądanie działania akcji i filtrów

Najwygodniej oczywiście śledzić to, co robi WordPress przy pomocy debuggera.

wp_filters

Podgląd tablicy $wp_filter w środowisku PHPStorm (debugger XDebug)

Na powyższym obrazku zatrzymaliśmy się w pliku index.php na początku pętli głównej WordPressa i widzimy, że w tablicy $wp_filter znajduje się 212 haków dla których jest zarejestrowana jakaś akcja lub filtr. Dla haka 'term_name' mamy łącznie 4 filtry, jeden z priorytetem 30 i trzy z domyślnym priorytetem 10. Jeśli wydaje Ci się, że to dużo, jesteś w błędzie. Podglądamy własnie działanie niemal czystej instancji WordPressa, której używam do testowania przykładów do tego serwisu. Ma zainstalowane raptem kilka wtyczek i używa stosunkowo prostego motywu. Gdyby to był motyw oparty na złożonym frameworku i gdybyśmy zainstalowali kilkanaście wtyczek, co jest typowe dla większości działających serwisów, te liczby byłyby znacznie większe.

Zaglądnijmy też do tablicy $wp_actions

wp_actions

Podgląd tablicy $wp_actions w środowisku PHPStorm (debugger XDebug)

Widzimy, że podczas tego otwarcia strony, zanim jeszcze zaczęła się wykonywać główna pętla, WordPress wywołał już akcje da 29 różnych haków, w tym niektóre, te związane z rejestracją widgetów, typów postów i taksonomii zostały już wywołana po kilkanaście razy.

Jeśli na poważnie myślisz o programowaniu w PHP, w tym o programowaniu wtyczek czy motywów dla WordPressa zdecydowanie polecam skonfigurowanie debuggera. Raz poświęcony na to czas, zwróci się z nawiązką. XDebug to darmowe rozwiązanie, które da się użyć niemal w każdej konfiguracji jaką można sobie wyobrazić do takiej pracy.

Jeśli jednak z jakiegoś powodu nie chcesz lub nie możesz użyć debuggera, zawsze możesz wypisać zawartość zmiennych związanych z hakami w jakimś miejscu kodu. Możesz do tego użyć na przykład poniższej funkcji:

function test_dump_wp_filter()
{
  global $wp_filter;
  $hook_cnt = 0;
  $filter_cnt = 0;
  echo '<ul>';
  foreach ( $wp_filter as $this_tag => $priorities ) {
    $hook_cnt++;
    echo '<li><p>' . $this_tag . '</p>';

    foreach ( $priorities as $this_priority => $filters ) {
      echo '<ul>';

      foreach ( $filters as $this_filter => $filter_data ) {
        $filter_cnt++;
        echo '<li>';

        if (is_object($filter_data['function']) )
          echo '{ closure }';
        elseif (is_array($filter_data['function']) )
          echo get_class($filter_data['function'][0]).' :: '.
               $filter_data['function'][1];
        else
          echo $filter_data['function'];

        echo ' - '.$this_priority.', '.
             $filter_data['accepted_args'].'</li>';
        echo '</li>';
      }
      echo '</ul>';
    }
    echo '</li>';
  }
  echo '</ul>';
  echo "<p>Obsłużonych haków: $hook_cnt, ".
       "liczba fitrów i akcji: $filter_cnt</p>";
}

Funkcja wypisuje zawartość $wp_filter. Możesz umieścić jej wywołanie w szablonie strony, np. zaraz po wywołaniu funkcji the_content, wtedy wszystko zostanie wypisane pod aktualnie wyświetlanym artykułem.

Oczywiście mankamentem takiego rozwiązania jest to, że nie zawsze możemy wypisać coś w trakcie wykonywania strony w WordPressie. W takim wypadku wygodne jest zrzucenie takiego wyniku do tymczasowego pliku HTML i oglądanie niezależnie w przeglądarce. Można to zrobić dodając do functions.php następującą funkcję

function test_to_file ($func, $filename = 'c:\\temp\\dump.html')
{
  $buffer = '<html lang="pl-PL">'.
            '<head><meta charset="UTF-8" /></head><body>';
  if (is_callable($func)) {
    ob_start();
    $func();
    $buffer .= ob_get_clean();
  }
  else
    $buffer .= 'Nie można wywołać funkcji:<pre>$func = '.
  print_r($func, true).'</pre>';
  $buffer .= '</body></html>';
  $h = fopen($filename,'w');
  fwrite($h, $buffer);
  fclose($h);
}

Funkcja przechwytuje wyjście z innej funkcji podanej w parametrze $func i wypisuje do pliku $filename (domyślnie do c:\temp\dump.html), dodając opakowanie HTMLowe, bo zakładamy, że funkcja $func, podobnie jak nasza test_dump_wp_filter wypisuje na wyjściu jakiś kawałek HTMLa. Teraz wystarczy gdzieś w kodzie zamiast:

test_dump_wp_filter();

wywołać:

test_to_file('test_dump_wp_filter');

Funkcję test_to_file można użyć do wypisania do pliku HTML innych zrzutów zmiennych i danych testowych, jednak na dłuższą metę, jeśli tylko jest to możliwe technicznie, należy zdecydowanie zacząć korzystać z debuggera.

W naszym wypadku otrzymaliśmy całkiem spory plik HTML kończący się następująco:

Obsłużonych haków: 214, liczba fitrów i akcji: 371

Jeśli chcemy prześledzić w jakiej kolejności WordPress wywołuje akcje i filtry dla poszczególnych haków, albo przechwycić moment obsługi danego haka, możemy się posłużyć uniwersalnym hakiem 'all'. Jeśli dodamy dla niego akcję, będzie ona wywoływana przy każdym wywołaniu apply_filters, do_action, apply_filters_ref_array i do_action_ref_array. Najprostsza obsługa haka 'all' wygląda następująco.

function test_filter_trace() {
  $args = func_get_args();
  $tag = array_shift( $args );
  error_log($tag);
}

add_action( 'all', 'test_filter_trace');

W wyniku jej działania w error logu PHP otrzymamy wpisy dla każdego wywołania dowolnego filtra lub akcji (ich nazwy). W moim przypadku, w bardzo ubogim funkcjonalnie, przykładowym serwisie, w którym zainstalowałem raptem kilka wtyczek i korzystam ze stosunkowo prostego motywu, podczas całego procesu wykonania strony przez WordPressa otrzymałem w logu ponad 1800 wpisów. Można się spodziewać, że w bardziej dojrzałym serwisie będzie ich dobre kilka tysięcy.

Oczywiście możemy w tej akcji robić bardziej ambitne rzeczy, np. skorzystać z funkcji debug_backtrace aby dla każdego wywoływanego filtra i akcji dostać pełny stos wywołań.

Twoje własne haki

Oprócz korzystania z haków WordPressa, w wielu przypadkach możesz rozważyć zdefiniowanie własnych. Nie jest to bardzo użyteczne jeśli pracujesz tylko w obrębie własnego serwisu, rozwijasz go sam dla siebie i dodajesz funkcjonalności. Staje się jednak wręcz niezbędne jeśli przygotowujesz własny motyw lub wtyczkę, które chcesz dystrybuować szerzej, umieścić w repozytorium WordPressa lub sprzedawać. W takim wypadku zdefiniowanie własnych haków to często idealnny wybór.

Wielu developerów przygotowując wtyczki lub motywy stara się dostarczyć ekrany z ustawieniami dosłownie do wszystkiego. Mniejsza o to, że jest to bardzo pracochłonne i w efekcie kosztowne. Problem w tym, że bardzo często w ten sposób robią produkt, który jest ciężki i niewygodny dla wszystkich.

Dla zwykłego użytkownika, który nie jest programistą, takie rozbudowane ustawienia są często odstraszające, zwykle większość z nich jest niezrozumiała, z wielu opcji użytkownik nie potrafi korzystać.

Z kolei dla programisty, który natychmiast będzie chciał coś więcej niż daje dana wtyczka, te ekrany są raczej obciążeniem. Dla niego znacznie prostsze jest samemu dopisać nowe funkcjonalności. Nie marzy o rozbudowanych ustawieniach tylko otwartości, elastyczności, możliwość podpięcia się w dowolne miejsce.

I tutaj właśnie jest idealne miejsce dla Twoich haków.

Planując nową wtyczkę zastanów się, w jakim stopniu powinna być konfigurowalna przy pomocy ustawień. Na pewno powinny to być rzeczy, które są zrozumiałe dla większości potencjalnych użytkowników. Na pewno jest to też dobry pomysł, jeśli dajesz kilka gotowych rozwiązań do wyboru i nawet jeśli są to rzeczy skomplikowane, użytkownik łatwo zrozumie czym się różnią metodą prób i błędów.

Jeśli jednak chcesz zapewnić większą elastyczność, na przykład po to, aby inni programiści mogli zmieniać sposób w jaki wtyczka wyświetla to, co robi, nie brnij w wymyślanie własnego języka templatów, nie brnij w rozbudowane ekrany do ustawiania różnych aspektów wizualnych tego co robi wtyczka. Daj programistom bardzo dobrze przemyślany i zrobiony w duchu WordPressa arkusz styli. Jeśli chcesz więcej, żeby możliwa była całkowita zmiana tego jak wtyczka prezentuje dane, albo jak się zachowuje, dostarcz przemyślanych haków.

Można zupełnie zmienić sposób w jaki działa WordPress, kompletnie wywrócić do góry nogami wygląd poszczególnych elementów, powtykać do serwisu moduły, o których twórcom WordPress się nie śniło. To wszystko jest możliwe nie dlatego, że WordPress daje sto stron z ustawieniami. Daje ich stosunkowo niedużo, jak na swoje możliwości i zakres działania. Często jeden motyw czy wtyczka maja ich więcej. To wszystko jest możliwe dzięki prostemu, ale wygodnemu mechanizmowi haków oraz dzięki temu, że są one wywoływane dosłownie w każdym miejscu gdzie może to być przydatne. Tworząc własne wtyczki i motywy korzystaj z tego systemu i wzoruj się jeśli chodzi o sposób jego użycia na tym, co robią twórcy WordPressa.

Powiadomimy Cię o nowych artykułach

Komentarzy: 6

  1. witam, dopiero uczę się wordpressa i mam problem, zainstalowałem wtyczkę wp catalogue (katalog produktów), i powoli ja przerabiam, niestety wtyczka z tego co się dowiedziałem od autora wtyczki nie posiada wbudowanej obsługi tagów, to co chciał bym zrobić w tej wtyczce to dodać możliwość filtrowania produktów w katalogu, za pomocą rozwijanej listy. czyli zeby była lista w której były by nastepujące opcje: wszystkie marki, marka 1, marka 2, w związku z tym że wtyczka nie obsługuje tagów, utworzyłem własne pole o nazwie product_marka z odpowiednimi markami, i teraz pytanie jak mogę filtrować w tej wtyczce produkty po nazwie własnego pola? czy jest to możliwe? Dziękuję za ewentualną pomoc i pozdrawiam .

    • 1. Pojęcie filtrów w tytule tego tekstu nie ma nic wspólnego z filtowaniem danych, o którym piszesz. To przypadkowa zbieżność słowa.

      2. Nie znam wspomnianej przez Ciebie wtyczki (wszystkich wtyczek jest kilkadziesiąt tysięcy, nie sposób znać wszystkich), a dopisanie mechanizmów, o których wspomniałeś nie jest tak proste, żeby dokładnie odpisać w komentarzu.

      3. Wydaje mi się jednak, że droga która powinieneś pójść jest raczej nie dodawanie pola, ale dodanie taksonomii użytkownika do produktu (czyli czegoś co zachowuje się jak tag, czy kategoria). W takim wypadku WordPress z definicji pozwoli Ci oglądać produkty zawężone do danego tagu przez: domena_serwisu/nazwa_taksonomii/nazwa_pojęcia i właściwie pozostanie Ci zrobić tylko rozwijaną listę wypełnioną pojęciami tej taksonomii. Nie wiem jak to się zderzy z funkcjonalnościami wtyczki, o której piszesz, ale jest spora szansa, że dasz radę, jeśłi jesteś w miarę sprawny w programowaniu.

  2. Dzięki za podpowiedź i szybką reakcję na moje pytanie. 😉

  3. witam, mam jeszcze jedno pytanie zrobilem swoja taxonomie marka, ale nie zadobrze to dziala, z tego co widzę to kategorie sa chyba tez zrobione jako wlasna taxonomia, czytalem tez o multi taxonomi ale nie specjalnie to dziala.

    // products area
    $per_page = get_option(‚pagination’);
    if($per_page==0){
    $per_page = „-1”;
    }

    //
    $term_slug = get_queried_object()->slug;
    if($term_slug){
    $args = array(
    ‚post_type’=> ‚wpcproduct’,
    ‚order’ => ‚ASC’,
    ‚orderby’ => ‚menu_order’,
    ‚posts_per_page’ => $per_page,
    ‚paged’ => $paged,
    ‚tax_query’ => array(
    array(
    ‚taxonomy’ => ‚wpccategories’,
    ‚taxonomy’ => ‚marka’,
    ‚field’ => ‚slug’,
    ‚terms’ => get_queried_object()->slug
    )
    ));

    }else{
    $args = array(
    ‚post_type’=> ‚wpcproduct’,
    ‚order’ => ‚ASC’,
    ‚orderby’ => ‚menu_order’,
    ‚posts_per_page’ => $per_page,
    ‚paged’ => $paged,
    );
    }

  4. po dodaniu ‚taxonomy’ => ‚marka’ dziala ale raz kategoria a raz marka …. hmmm

  5. Witam.
    Wszystko świetnie wyjaśnione, jednak dla laika może się pojawić problem z zastosowaniem w praktyce 😉 Załóżmy że chcemy ograniczyć komentowanie, poprzez dodanie własnego filtra pre comment approved i warunek, że zalogowany użytkownik może dodać komentarz bez akceptacji przez admina, dopiero po 30 swoich komentarzach i 30 dniach. W każdym innym przypadku komentarze akceptuje admin.
    Pytanie – jak zastosować taki warunek w filtrze, skąd wziąć informacje o tym, ile komentarzy do tej pory dodał user i że jest już 30 dni ?