WordPress Hooks

WordPress Hooks – przewodnik po actions i filters

WordPress Hooks (po polsku: haki, punkty zaczepienia) to fundament całego systemu WordPress. To mechanizm, który pozwala modyfikować i rozszerzać WordPress bez edytowania jego kodu źródłowego. Zrozumienie hooków WordPress to różnica między kopiowaniem kodu z Stack Overflow bez zrozumienia, a świadomym developmentem (programowaniem).

Patrzysz na kod WordPress i widzisz do_action(), add_filter(), apply_filters(). Wiesz, że to ważne, ale nie do końca rozumiesz JAK to działa i PO CO?

Albo chcesz dodać custom funkcjonalność do WordPress, ale nie wiesz gdzie „zahaczić” swój kod? Edytujesz pliki core WordPress (❌ nigdy tego nie rób!) bo nie znasz lepszego sposobu?

W tym przewodniku nauczysz się dokładnie czym są hooki WordPress, jak działają actions (akcje) i filters (filtry), poznasz najpopularniejsze przykłady, i nauczysz się tworzyć własne custom hooks (niestandardowe haki). Po przeczytaniu będziesz programować WordPress jak profesjonalista.

Podstawy hooków – event-driven architecture

Czym są WordPress Hooks?

Hook (hak, punkt zaczepienia) = miejsce w kodzie WordPress, gdzie możesz „zahaczyć” swoją funkcję.

Analogia: System kontroli lotów:

Samolot startuje:
├─ Wydarzenie: "start" (event)
├─ System kontroli: powiadamia wszystkich zainteresowanych
├─ Wieża kontrolna: "zanotuj start" (subscriber 1)
├─ Radar: "śledź pozycję" (subscriber 2)
└─ System logów: "zapisz do historii" (subscriber 3)

WordPress działa podobnie:
├─ Wydarzenie: użytkownik publikuje post
├─ WordPress: uruchamia hook 'publish_post'
├─ Plugin SEO: "aktualizuj sitemap" (subscriber 1)
├─ Plugin social: "wyślij na Twitter" (subscriber 2)
└─ Twój kod: "wyślij email do admina" (subscriber 3)

Zero konfliktów. Każdy robi swoje. Piękne!

Definicja techniczna: Hook WordPress to mechanizm event-driven (sterowany zdarzeniami) pozwalający na wykonanie custom kodu w określonych momentach cyklu życia WordPress bez modyfikowania plików core.

Actions vs. Filters – kluczowa różnica

2 typy hooków:

1. Actions (Akcje) – „Zrób coś w tym momencie”

// WordPress mówi: "Właśnie opublikowano post"
do_action('publish_post', $post_ID);

// Ty odpowiadasz: "OK, wyślę email"
add_action('publish_post', 'send_notification_email');
function send_notification_email($post_ID) {
    // Twój kod wysyłający email
    wp_mail('admin@example.com', 'Nowy post!', 'Post #' . $post_ID);
}

// Action = efekt uboczny: email wysłany, nie zwraca wartości

2. Filters (Filtry) – „Zmodyfikuj tę wartość”

// WordPress mówi: "Mam content posta, może ktoś chce go zmienić?"
$content = apply_filters('the_content', $content);

// Ty odpowiadasz: "Tak! Dodam przyciski udostępniania"
add_filter('the_content', 'add_share_buttons');
function add_share_buttons($content) {
    $buttons = '<div class="share">Share: FB, Twitter</div>';
    return $content . $buttons; // ZWRACASZ zmodyfikowaną wartość
}

// Filter = transformacja: treść na WEJŚCIU → zmodyfikowana treść na WYJŚCIU

Kluczowa różnica:

ACTION:
- Wykonuje akcję (efekt uboczny)
- NIE zwraca wartości
- Przykłady: wyślij email, zapisz do DB, utwórz plik

FILTER:
- Modyfikuje wartość
- MUSI zwrócić wartość (return)
- Przykłady: zmień tytuł, dodaj CSS class, usuń HTML

Analogia:

Action = Alarm przeciwpożarowy
├─ Gdy wykryje dym: uruchamia się (do_action)
├─ System tryskaczowy: rozpyla wodę (reakcja 1)
├─ System powiadamiający: dzwoni na straż (reakcja 2)
└─ NIE zmienia dymu, po prostu reaguje

Filter = Filtr kawy
├─ Woda wchodzi (apply_filters)
├─ Przechodzi przez kawę (Twoja funkcja)
└─ Wychodzi jako kawa (zwrócona wartość)

Anatomia hooka – jak to działa pod maską

Krok po kroku:

1. WordPress definiuje hook (w swoim kodzie core):

// W wp-includes/post.php (przykład uproszczony)
function wp_publish_post($post_ID) {
    // ... logika publikacji ...

    // HOOK: WordPress mówi "hej, ktoś chce wiedzieć o publikacji?"
    do_action('publish_post', $post_ID, $post);

    // ... reszta logiki ...
}

2. Ty „zahaczasz” swoją funkcję (w functions.php lub wtyczce):

// Rejestruj swoją funkcję do tego hooka
add_action(
    'publish_post',              // Nazwa hooka (gdzie zahaczyć)
    'my_custom_function',        // Twoja funkcja (co wykonać)
    10,                          // Priorytet (kolejność, domyślny 10)
    2                            // Liczba argumentów (przyjmie $post_ID i $post)
);

function my_custom_function($post_ID, $post) {
    // Twój kod wykonuje się TERAZ
    error_log("Opublikowano post #" . $post_ID);
}

3. WordPress wywołuje hook w czasie działania:

Użytkownik klika "Opublikuj"
↓
WordPress: wp_publish_post()
↓
Wewnątrz: do_action('publish_post', ...)
↓
WordPress sprawdza: "Kto zahaczy na 'publish_post'?"
↓
Znajduje: my_custom_function (priorytet 10)
↓
Wykonuje: my_custom_function($post_ID, $post)
↓
Twój kod się uruchamia!
↓
Kontynuacja: reszta wp_publish_post()

Kluczowe: Twój kod wykonuje się między kodem WordPress, nie zamiast niego. To jest rozszerzenie, nie nadpisanie.

Gdzie dodawać hooki?

3 główne miejsca:

1. Theme functions.php

// wp-content/themes/twoj-motyw/functions.php

add_action('wp_enqueue_scripts', 'my_scripts');
function my_scripts() {
    wp_enqueue_style('my-style', get_stylesheet_uri());
}

// Dla: Theme-specific funkcjonalności
// Uwaga: Znika po zmianie motywu!

2. Wtyczka niestandardowa

// wp-content/plugins/moj-plugin/moj-plugin.php

/**
 * Plugin Name: Mój Custom Plugin
 * Description: Moje hooкi i funkcje
 * Version: 1.0
 */

add_action('init', 'my_custom_post_type');
function my_custom_post_type() {
    // Rejestracja custom post type
}

// Dla: W całej witrynie funkcjonalności
// Zaleta: Niezależne od motywu

3. Wtyczka niezbędna do użycia (MU)

// wp-content/mu-plugins/always-on.php

add_filter('upload_mimes', 'allow_svg');
function allow_svg($mimes) {
    $mimes['svg'] = 'image/svg+xml';
    return $mimes;
}

// Dla: Funkcjonalności, które MUSZĄ być zawsze aktywne
// Uwaga: Nie można wyłączyć z panelu admin

Najlepsza praktyka:

  • Specyficzne dla motywu = functions.php
  • Wielokrotnego użytku = wtyczka
  • Krytyczne = MU wtyczka

Najpopularniejsze Actions – praktyczne przykłady

init – inicjalizacja WordPress

Kiedy się uruchamia: Po załadowaniu WordPress, przed wysłaniem headers (nagłówków).

Typowe użycia:

  • Rejestracja niestandardowych typów wpisów
  • Rejestracja taksonomii
  • Dodawanie reguł przepisywania
  • Inicjalizacja sesji

Przykład 1: Niestandardowy typ wpisu (CTP)

add_action('init', 'register_portfolio_cpt');
function register_portfolio_cpt() {
    register_post_type('portfolio', [
        'labels' => [
            'name' => 'Portfolio',
            'singular_name' => 'Projekt'
        ],
        'public' => true,
        'has_archive' => true,
        'supports' => ['title', 'editor', 'thumbnail'],
        'rewrite' => ['slug' => 'projekty']
    ]);
}

Przykład 2: Niestandardowa taksonomia

add_action('init', 'register_project_category');
function register_project_category() {
    register_taxonomy('project_cat', 'portfolio', [
        'labels' => [
            'name' => 'Kategorie Projektów',
            'singular_name' => 'Kategoria'
        ],
        'hierarchical' => true, // Jak kategorie (nie tagi)
        'public' => true,
        'rewrite' => ['slug' => 'kategoria-projektu']
    ]);
}

wp_enqueue_scripts – ładowanie CSS/JS

Kiedy się uruchamia: Gdy WordPress ładuje skrypty i style dla frontendu.

Dlaczego nie zakodować na stałe w header.php?

❌ ŹLE (w header.php):
<link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/style.css">
<script src="<?php echo get_template_directory_uri(); ?>/script.js"></script>

Problemy:
- Brak kontroli kolejności
- Brak zależności
- Konfliktność z wtyczkami
- Nie respektuje wp_head()

✅ DOBRZE przez hooki

Przykład: Kolejkowanie stylu i skryptu

add_action('wp_enqueue_scripts', 'my_theme_assets');
function my_theme_assets() {
    // Style
    wp_enqueue_style(
        'my-theme-style',                                  // Uchwyt - unikalny ID
        get_stylesheet_directory_uri() . '/css/style.css', // URL
        [],                                                // Zależności - puste
        '1.0.0',                                           // Wersja dla omijania pamięci podręcznej
        'all'                                              // Media (all/screen/print)
    );

    // Scripts
    wp_enqueue_script(
        'my-theme-script',                             // Handle - uchwyt
        get_template_directory_uri() . '/js/main.js',  // URL
        ['jquery'],                                    // Zależność: wymaga jQuery (załaduje jQuery najpierw)
        '1.0.0',                                       // Wersja
        true                                           // W stopce? true = przed </body>, false = w <head>
    );

    // Warunkowe kolejkowanie - tylko na single post
    if (is_single()) {
        wp_enqueue_script('comments-script', get_template_directory_uri() . '/js/comments.js', ['jquery'], '1.0', true);
    }
}

Zlokalizuj skrypt (przekazywanie danych PHP → JS):

add_action('wp_enqueue_scripts', 'my_ajax_setup');
function my_ajax_setup() {
    wp_enqueue_script('my-ajax', get_template_directory_uri() . '/js/ajax.js', ['jquery'], '1.0', true);

    // Przekaż dane z PHP do JavaScript
    wp_localize_script('my-ajax', 'myAjax', [
        'ajaxurl' => admin_url('admin-ajax.php'),
        'nonce' => wp_create_nonce('my_ajax_nonce'),
        'siteName' => get_bloginfo('name')
    ]);
}

// W JS będziesz mógł użyć:
// console.log(myAjax.siteName);
// $.post(myAjax.ajaxurl, { action: 'my_action', nonce: myAjax.nonce });

save_post – zapis wpisu

Kiedy się uruchamia: Po zapisaniu/aktualizacji posta (publish, draft, update).

Typowe użycia:

  • Zapisywanie niestandardowych pól
  • Aktualizacja powiązanej treści
  • Powiadomienia
  • Czyszczenie pamięci podręcznej

Przykład: Zapisz niestandardowe meta

add_action('save_post', 'save_custom_meta', 10, 3);
function save_custom_meta($post_id, $post, $update) {
    // 1. Sprawdzenia bezpieczeństwa

    // Automatyczne zapisywanie? Pomiń
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }

    // Weryfikacja
    if (!isset($_POST['my_meta_nonce']) || !wp_verify_nonce($_POST['my_meta_nonce'], 'my_meta_save')) {
        return;
    }

    // Uprawnienia użytkownika
    if (!current_user_can('edit_post', $post_id)) {
        return;
    }

    // 2. Zapisz meta
    if (isset($_POST['custom_field'])) {
        $value = sanitize_text_field($_POST['custom_field']); // ZAWSZE sanitize(czyścić)!
        update_post_meta($post_id, '_custom_field_key', $value);
    }

    // 3. Logika warunkowa
    if ($post->post_type === 'portfolio' && $post->post_status === 'publish') {
        // Akcja tylko dla publikuj portfolio items
        do_action('portfolio_published', $post_id);
    }
}

Warianty save_post:

// save_post_{post_type} - bardziej specyficzny
add_action('save_post_portfolio', 'save_portfolio_meta'); // Tylko dla CPT 'portfolio'

// wp_insert_post - uruchamia się PRZED save_post
add_action('wp_insert_post', 'before_save_logic');

wp_head i wp_footer – output do HTML

wp_head – Przed zamknięciem </head>; wp_footer – Przed zamknięciem </body>

Przykład 1: Dodaj meta tags

add_action('wp_head', 'custom_meta_tags');
function custom_meta_tags() {
    if (is_single()) {
        global $post;
        echo '<meta property="og:title" content="' . esc_attr(get_the_title()) . '">';
        echo '<meta property="og:description" content="' . esc_attr(wp_trim_words($post->post_content, 20)) . '">';
    }
}

Przykład 2: Google Analytics

add_action('wp_head', 'add_google_analytics');
function add_google_analytics() {
    ?>
    <!-- Google Analytics -->
    <script async src="<https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID>"></script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());
        gtag('config', 'GA_POMIAR_ID');
    </script>
    <?php
}

WAŻNE: Twó motyw MUSI mieć <?php wp_head(); ?> i <?php wp_footer(); ?>. Bez tego hooki nie zadziałają!

admin_menu – dodawanie menu w admin

Kiedy się uruchamia: Gdy WordPress buduje menu administracyjne.

Przykład: Niestandardowa strona administracyjna

add_action('admin_menu', 'add_custom_admin_page');
function add_custom_admin_page() {
    add_menu_page(
        'Moje Ustawienia',              // Tytuł strony
        'Moje Menu',                    // Tytuł w menu
        'manage_options',               // Uprawnienie - kto widzi
        'my-custom-page',               // Menu slug - unikalny ID
        'render_custom_admin_page',     // Callback - funkcja renderująca
        'dashicons-admin-generic',      // Ikona
        6                               // Pozycja - gdzie w menu
    );

    // Submenu (podmenu)
    add_submenu_page(
        'my-custom-page',               // slug rodzica
        'Pod-strona',                   // Tytuł strony
        'Dodatkowe',                    // Tytuł menu
        'manage_options',
        'my-subpage',
        'render_subpage'
    );
}

function render_custom_admin_page() {
    ?>
    <div class="wrap">
        <h1>Moja Custom Strona Admin</h1>
        <form method="post" action="options.php">
            <?php
            settings_fields('my_options_group');
            do_settings_sections('my-custom-page');
            submit_button();
            ?>
        </form>
    </div>
    <?php
}

widgets_init – rejestracja sidebar

Przykład: Niestandardowy sidebar

add_action('widgets_init', 'register_custom_sidebars');
function register_custom_sidebars() {
    register_sidebar([
        'name' => 'Sidebar Stopka',
        'id' => 'footer-sidebar',
        'description' => 'Widgety w stopce strony',
        'before_widget' => '<div class="footer-widget">',
        'after_widget' => '</div>',
        'before_title' => '<h3 class="widget-title">',
        'after_title' => '</h3>'
    ]);
}

// Użycie w motywie (footer.php):
<?php if (is_active_sidebar('footer-sidebar')) : ?>
    <aside class="footer-widgets">
        <?php dynamic_sidebar('footer-sidebar'); ?>
    </aside>
<?php endif; ?>

Filters w praktyce – modyfikacja danych

the_title – modyfikacja tytułu

Kiedy się uruchamia: Gdy WordPress wyświetla tytuł postata.

Przykład 1: Dodaj emoji do tytułów

add_filter('the_title', 'add_emoji_to_title', 10, 2);
function add_emoji_to_title($title, $post_id) {
    // Tylko dla postów (nie strony, nie admin)
    if (is_admin() || get_post_type($post_id) !== 'post') {
        return $title;
    }

    $emoji = '📝 ';
    return $emoji . $title;
}

Przykład 2: Dodaj „(Nowy!)” do tytułów z ostatnich 7 dni

add_filter('the_title', 'mark_new_posts');
function mark_new_posts($title, $post_id) {
    if (is_admin()) return $title;

    $post_date = get_the_date('U', $post_id); // Unix timestamp
    $current_time = current_time('timestamp');
    $days_old = ($current_time - $post_date) / DAY_IN_SECONDS;

    if ($days_old <= 7) {
        $title .= ' <span class="new-badge">NEW!</span>';
    }

    return $title;
}

the_content – modyfikacja treści

Kiedy się uruchamia: Gdy WordPress wyświetla treść posta.

Przykład 1: Dodaj przyciski udostępniania

add_filter('the_content', 'add_share_buttons');
function add_share_buttons($content) {
    // Tylko na pojedyncze posty
    if (!is_single()) {
        return $content;
    }

    $share_html = '<div class="share-buttons">';
    $share_html .= '<a href="<https://twitter.com/share?url=>' . urlencode(get_permalink()) . '" target="_blank">Tweet</a>';
    $share_html .= '<a href="<https://www.facebook.com/sharer.php?u=>' . urlencode(get_permalink()) . '" target="_blank">Share</a>';
    $share_html .= '</div>';

    // Dodaj PO treści
    return $content . $share_html;
}

Przykład 2: Czas czytania

add_filter('the_content', 'add_reading_time');
function add_reading_time($content) {
    if (!is_single()) return $content;

    $word_count = str_word_count(strip_tags($content));
    $reading_time = ceil($word_count / 200); // 200 słów/minutę

    $time_html = '<div class="reading-time">⏱️ Czas czytania: ' . $reading_time . ' min</div>';

    // Dodaj PRZED treścią
    return $time_html . $content;
}

excerpt_length i excerpt_more – skrót wpisu

Przykład: Niestandardowy fragment

// Zmień długość (domyślnie 55 słów)
add_filter('excerpt_length', 'custom_excerpt_length');
function custom_excerpt_length($length) {
    return 30; // 30 słów
}

// Zmień "więcej" (domyślnie "[...]")
add_filter('excerpt_more', 'custom_excerpt_more');
function custom_excerpt_more($more) {
    return '... <a href="' . get_permalink() . '" class="read-more">Czytaj więcej »</a>';
}

upload_mimes – dozwolone typy plików

Przykład: Dodaj SVG support

add_filter('upload_mimes', 'allow_svg_upload');
function allow_svg_upload($mimes) {
    $mimes['svg'] = 'image/svg+xml';
    $mimes['svgz'] = 'image/svg+xml';
    return $mimes; // ZAWSZE return w filtrze!
}

// UWAGA: SVG może zawierać złośliwy kod!
// Używaj tylko jeśli ufasz uploaderom.

login_errors – komunikaty logowania

Przykład: Ukryj szczegóły błędów (bezpieczeństwo)

add_filter('login_errors', 'generic_login_error');
function generic_login_error($error) {
    // Zamiast "Invalid username" lub "Wrong password"
    // Pokaż wiadomość ogólną
    return 'Nieprawidłowe dane logowania.';
}

// Dlaczego? Hakerzy nie wiedzą czy username istnieje czy nie

body_class – klasy CSS body

Przykład: Dodaj niestandardowe klasy

add_filter('body_class', 'custom_body_classes');
function custom_body_classes($classes) {
    // Dodaj klasę jeśli użytkownik zalogowany
    if (is_user_logged_in()) {
        $classes[] = 'logged-in-user';
    }

    // Dodaj klasy z typem urządzenia(wymaga MobileDetect library)
    if (wp_is_mobile()) {
        $classes[] = 'mobile-device';
    } else {
        $classes[] = 'desktop-device';
    }

    // Usuń niepotrzebne klasy
    $classes = array_diff($classes, ['unnecessary-class']);

    return $classes;
}

Custom Hooks – tworzenie własnych punktów zaczepienia

Dlaczego tworzyć własne hooki?

Scenariusz: Tworzysz wtyczkę, która wysyła email po wykonaniu akcji.

Bez niestandardowego hooka:

function my_plugin_send_email($user_id) {
    $user = get_userdata($user_id);
    wp_mail($user->user_email, 'Temat', 'Wiadomość');
}

// Problem: Inni developerzy nie mogą "zahaczyć" się do Twojego pluga
// Nie mogą dodać swojej logiki przed/po email

Z niestandardowym hookiem:

function my_plugin_send_email($user_id) {
    // PRZED wysłaniem - action hook
    do_action('before_my_email_send', $user_id);

    $user = get_userdata($user_id);

    // Pozwól innym zmodyfikować temat/treść - filter hook
    $subject = apply_filters('my_email_subject', 'Temat', $user_id);
    $message = apply_filters('my_email_message', 'Wiadomość', $user_id);

    wp_mail($user->user_email, $subject, $message);

    // PO wysłaniu - action hook
    do_action('after_my_email_send', $user_id);
}

// Teraz inni mogą "zahaczać":
add_action('before_my_email_send', function($user_id) {
    // Log przed wysłaniem
    error_log("Wysyłam email do user #" . $user_id);
});

add_filter('my_email_subject', function($subject, $user_id) {
    return $subject . ' - ' . date('Y-m-d');
}, 10, 2);

Korzyści niestandardowych hooków:

  • Rozszerzalność – inni mogą dodawać funkcje
  • Separacja odpowiedzialności – każda wtyczka robi swoje
  • Brak konfliktów – nie nadpisujesz kodu
  • Profesjonalny – standard WordPress

Tworzenie niestandardowego action hook

Struktura:

do_action('hook_name', $arg1, $arg2, ...);

Przykład: Niestandardowy typ posta – hook publikacji

// W swoim wtyczce/motyw
function publish_portfolio_item($post_id) {
    $post = get_post($post_id);

    if ($post->post_type !== 'portfolio' || $post->post_status !== 'publish') {
        return;
    }

    // CUSTOM HOOK - Tworzysz punkt zaczepienia
    do_action('portfolio_item_published', $post_id, $post);

    // Możesz też z różnymi danymi:
    do_action('portfolio_published_' . $post->post_status, $post_id);
}
add_action('save_post', 'publish_portfolio_item');

// Teraz inni mogą używać Twojego hooka:
add_action('portfolio_item_published', 'notify_admin_of_portfolio', 10, 2);
function notify_admin_of_portfolio($post_id, $post) {
    wp_mail(
        'admin@example.com',
        'Nowy projekt portfolio',
        'Opublikowano: ' . $post->post_title
    );
}

// Ktoś inny może dodać inną funkcję:
add_action('portfolio_item_published', 'update_portfolio_count');
function update_portfolio_count($post_id) {
    $count = get_option('portfolio_count', 0);
    update_option('portfolio_count', $count + 1);
}

// Zero konfliktów! Każdy robi swoje.

Tworzenie custom filter hook

Struktura:

$value = apply_filters('filter_name', $value, $arg1, $arg2, ...);

Przykład: Niestandardowe formatowanie cen

// Funkcja, która formatuje cenę
function format_product_price($price) {
    // Default formatting
    $formatted = number_format($price, 2, ',', ' ') . ' zł';

    // CUSTOM FILTER - pozwól innym zmodyfikować
    return apply_filters('my_product_price_format', $formatted, $price);
}

// Ktoś może to użyć, żeby dodać walutę EUR:
add_filter('my_product_price_format', 'add_eur_conversion', 10, 2);
function add_eur_conversion($formatted, $price_pln) {
    $eur_rate = 4.5; // Przykładowy kurs
    $price_eur = $price_pln / $eur_rate;
    $eur_formatted = number_format($price_eur, 2, ',', ' ') . ' €';

    return $formatted . ' (' . $eur_formatted . ')';
}

// Wynik: "100,00 zł (22,22 €)"

Przykład 2: Przetwarzanie treści niestandardowych

// Funkcja przetwarzająca niestandardową treść
function process_custom_content($content, $post_id) {
    // Podstawowe przetwarzanie
    $processed = wpautop($content); // Dodaj <p> tags

    // Filter 1: Pozwól modyfikować HTML
    $processed = apply_filters('before_custom_content_display', $processed, $post_id);

    // Twoja logika
    $processed = do_shortcode($processed); // Process shortcodes

    // Filter 2: Finalna modyfikacja
    $processed = apply_filters('custom_content_output', $processed, $post_id, $content);

    return $processed;
}

// Użycie filtrów:
add_filter('before_custom_content_display', 'add_table_of_contents');
function add_table_of_contents($content, $post_id) {
    if (str_word_count(strip_tags($content)) > 500) {
        $toc = generate_toc($content); // Custom function
        return $toc . $content;
    }
    return $content;
}

Najlepsze praktyki dla niestandardowego hooks

1. Konwencja nazewnictwa

// ✅ DOBRE:
do_action('myplugin_before_save');
do_action('mytheme_after_header');
apply_filters('mycompany_product_price');

// ❌ ŹLE (za ogólne, konflikt!):
do_action('before_save');
do_action('header');
apply_filters('price');

// Zasada: prefix_descriptive_name

2. Dokumentacja

/**
 * Odpala po opublikowaniu elementu portfolio
 *
 * @since 1.0.0
 *
 * @param int     $post_id  The post ID
 * @param WP_Post $post     The post object
 */
do_action('myplugin_portfolio_published', $post_id, $post);

3. Przekazuj użyteczne argumenty

// ✅ DOBRE - pełny kontekst:
do_action('order_completed', $order_id, $order_object, $user_id);

// ❌ ŹLE - za mało info:
do_action('order_completed', $order_id);
// Teraz developerzy muszą sami pobierać resztę danych

4. Używaj spójnie

// Jeśli masz "before", zrób też "after"
do_action('myplugin_before_import');
// ... logika importu ...
do_action('myplugin_after_import');

// To pozwala na opakowywanie logiki:
add_action('myplugin_before_import', 'start_timer');
add_action('myplugin_after_import', 'end_timer_and_log');

Zaawansowane techniki – hooki dla profesjonalistów

Priorytety i kolejność wykonania

Priorytet decyduje o kolejności wykonania funkcji na tym samym hooku.

// Default priority = 10
add_action('init', 'function_a'); // Priority 10 (default)
add_action('init', 'function_b', 5); // Priority 5 (wcześniej!)
add_action('init', 'function_c', 20); // Priority 20 (później!)

// Kolejność wykonania: function_b (5) → function_a (10) → function_c (20)

Przykład praktyczny:

// Plugin A: Rejestruje CPT
add_action('init', 'plugin_a_register_cpt', 5);

// Plugin B: Dodaje taksonomię do tego CPT
// MUSI wykonać się PO plugin A!
add_action('init', 'plugin_b_add_taxonomy', 10);

// Plugin C: Modyfikuje labels CPT
// MUSI wykonać się PO plugin A, ale może przed lub po B
add_action('init', 'plugin_c_modify_labels', 7);

// Kolejność: A (5) → C (7) → B (10)

Kiedy używać niskiego priorytetu:

  • Musisz wykonać się WCZEŚNIE (przed innymi)
  • Przykład: Rejestracja post types, taxonomies

Kiedy używać wysokiego priorytetu:

  • Musisz wykonać się PÓŹNIEJ (po innych)
  • Przykład: Finalne modyfikacje, sprzątanie

Liczba argumentów

4-ty parametr add_action() / add_filter() określa ile argumentów przekazać.

// Hook przekazuje 3 argumenty:
do_action('save_post', $post_ID, $post, $update);

// Jeśli chcesz je wszystkie:
add_action('save_post', 'my_function', 10, 3); // 3 = przyjmij wszystkie 3
function my_function($post_ID, $post, $update) {
    // Masz dostęp do wszystkich 3
}

// Jeśli potrzebujesz tylko pierwszego:
add_action('save_post', 'my_simple_function'); // Default = 1 argument
function my_simple_function($post_ID) {
    // Tylko $post_ID, reszta ignorowana
}

Praktyczny przykład:

// WordPress filter 'the_title' przekazuje 2 argumenty:
// $title, $post_id

// Jeśli potrzebujesz oba:
add_filter('the_title', 'modify_title_with_id', 10, 2);
function modify_title_with_id($title, $post_id) {
    if (get_post_type($post_id) === 'portfolio') {
        return '🎨 ' . $title;
    }
    return $title;
}

// Jeśli tylko title:
add_filter('the_title', 'simple_title_mod');
function simple_title_mod($title) {
    return strtoupper($title); // Wszystkie CAPSLOCK
}

Usuwanie hooków (unhooking)

remove_action() / remove_filter()

// Jakiś plugin dodał hook:
add_action('wp_footer', 'annoying_plugin_footer');

// Chcesz go usunąć:
remove_action('wp_footer', 'annoying_plugin_footer');

// UWAGA: Priorytety muszą się zgadzać!
// Jeśli wtyczka użyła:
add_action('wp_footer', 'annoying_plugin_footer', 20);

// Musisz użyć tego samego priorytetu:
remove_action('wp_footer', 'annoying_plugin_footer', 20);

Usuwanie hooka dodanego w klasie:

// Wtyczka używa klasę:
class SomePlugin {
    public function __construct() {
        add_action('init', [$this, 'init_function']);
    }

    public function init_function() {
        // ...
    }
}
$plugin_instance = new SomePlugin();

// Aby usunąć:
global $plugin_instance; // Musisz mieć dostęp do instancji
remove_action('init', [$plugin_instance, 'init_function']);

// Trudne! Dlatego dobre wtyczki oferują swoje własne hooki do wyłączania funkcji.

Warunkowe usuwanie:

add_action('wp_head', 'remove_unwanted_hooks');
function remove_unwanted_hooks() {
    // Usuń generator meta tag (ukryj wersję WP)
    remove_action('wp_head', 'wp_generator');

    // Usuń RSD link (niepotrzebny dla większości)
    remove_action('wp_head', 'rsd_link');

    // Usuń wlwmanifest
    remove_action('wp_head', 'wlwmanifest_link');

    // Usuń shortlink
    remove_action('wp_head', 'wp_shortlink_wp_head');

    // Wyczyść <head> dla lepszej wydajności
}

has_action / has_filter – sprawdzanie hooków

// Sprawdź czy jakiś hook jest zarejestrowany
if (has_action('init', 'my_custom_function')) {
    // Hook istnieje
    echo "Hook zarejestrowany!";
}

// Zwraca priorytet jeśli istnieje, false jeśli nie
$priority = has_filter('the_content', 'my_content_filter');
if ($priority !== false) {
    echo "Filter zarejestrowany z priority: " . $priority;
}

// Praktyczne użycie - warunkowa logika:
if (!has_action('wp_footer', 'google_analytics')) {
    // Dodaj GA tylko jeśli inny plugin jeszcze nie dodał
    add_action('wp_footer', 'my_google_analytics');
}

did_action – ile razy wykonano action

// Sprawdź ile razy action został wykonany
$count = did_action('init');
echo "Hook 'init' wykonał się " . $count . " razy";

// Praktyczne użycie - zapobieganie wielokrotności:
add_action('wp_footer', 'add_footer_script');
function add_footer_script() {
    static $done = false;
    if ($done) return; // Już wykonane, skip

    // ... kod ...

    $done = true;
}

// Lub sprawdź czy hook w ogóle się wykonał:
add_action('wp_footer', 'check_if_init_ran');
function check_if_init_ran() {
    if (did_action('init') === 0) {
        // 'init' jeszcze się nie wykonał?! Dziwne...
        error_log('Init not run yet in footer?!');
    }
}

Debugging hooków – narzędzia i techniki

Query Monitor wtyczka

Najlepsze narzędzie do debugowania WordPress.

Instalacja:
Dashboard → Plugins → Add New → "Query Monitor"

Co pokazuje:
├─ Wszystkie hooki wykonane na stronie
├─ Funkcje zahaczone do każdego hooka
├─ Kolejność wykonania (priority)
├─ Który plik dodał hook (plugin/theme)
└─ Czas wykonania każdej funkcji

Użycie:
1. Zainstaluj i aktywuj
2. Otwórz stronę
3. Kliknij "Query Monitor" w admin bar (góra)
4. Zakładka "Hooks & Actions"
5. Zobacz wszystkie hooki!

var_dump i error_log

// Sprawdź co przychodzi do funkcji:
add_filter('the_title', 'debug_title');
function debug_title($title) {
    error_log("Title received: " . $title);
    error_log("Backtrace: " . print_r(debug_backtrace(), true));
    return $title;
}

// Sprawdź zahaczane funkcje:
add_action('init', function() {
    global $wp_filter;
    error_log("Functions on 'the_content':");
    error_log(print_r($wp_filter['the_content'], true));
});

Niestandardowy pomocnik debugowania

// Funkcja helper do debugowania hooków
function debug_hook($hook_name) {
    global $wp_filter;

    if (!isset($wp_filter[$hook_name])) {
        echo "Hook '$hook_name' nie istnieje lub nie ma funkcji.";
        return;
    }

    echo "<h3>Hook: $hook_name</h3>";
    echo "<table border='1'>";
    echo "<tr><th>Priority</th><th>Function</th><th>Args</th></tr>";

    foreach ($wp_filter[$hook_name]->callbacks as $priority => $functions) {
        foreach ($functions as $function) {
            $callback = $function['function'];

            // Format callback name
            if (is_array($callback)) {
                $name = get_class($callback[0]) . '::' . $callback[1];
            } elseif (is_string($callback)) {
                $name = $callback;
            } else {
                $name = 'Closure/Anonymous';
            }

            echo "<tr>";
            echo "<td>$priority</td>";
            echo "<td>$name</td>";
            echo "<td>{$function['accepted_args']}</td>";
            echo "</tr>";
        }
    }

    echo "</table>";
}

// Użycie:
add_action('wp_footer', function() {
    if (current_user_can('manage_options')) {
        debug_hook('the_content');
    }
});

Podsumowanie – twoja droga do master hooków

WordPress Hooks to nie tylko funkcja – to filozofia WordPress. Rozszerzalność bez modyfikacji core. Separacja odpowiedzialności. Architektura sterowana zdarzeniami.

Quick reference – ściągawka

Actions – najpopularniejsze:

init                  - Inicjalizacja (CPT, taxonomies)
wp_enqueue_scripts    - Ładowanie CSS/JS (frontend)
admin_enqueue_scripts - Ładowanie CSS/JS (admin)
save_post             - Zapis/update posta
wp_head               - Output do <head>
wp_footer             - Output przed </body>
admin_menu            - Dodawanie stron admin
widgets_init          - Rejestracja sidebars/widgets
wp_login              - Po zalogowaniu użytkownika

Filters – najpopularniejsze:

the_title             - Modyfikacja tytułu
the_content           - Modyfikacja treści
the_excerpt           - Modyfikacja skrótu
excerpt_length        - Długość skrótu
excerpt_more          - Tekst "więcej"
body_class            - Klasy CSS <body>
upload_mimes          - Dozwolone typy plików
login_errors          - Komunikaty logowania

Action plan – ucz się praktycznie

PodstawyTydzień 1
□ Przeczytaj ten przewodnik
□ Zainstaluj Query Monitor
□ Eksperymentuj z 3 actions (init, wp_head, save_post)
□ Eksperymentuj z 3 filters (the_title, the_content, body_class)

PraktykaKolejny tydzień
□ Zbuduj custom funkcjonalność używając hooków
□ Przykład: Dodaj „time to read” do postów (filter: the_content)
□ Przykład: Wyślij email po publikacji (action: publish_post)

Tydzień 3: Zaawansowane
□ Eksperymentuj z priorytetem
□ Naucz się usuwać hooki (remove_action/remove_filter)
□ Zbuduj własny custom hook

Tydzień 4: Master level
□ Przeanalizuj kod popularnego plugina (np. Yoast SEO)
□ Zobacz jak profesjonaliści używają hooków
□ Zastosuj najlepsze praktyki w swoich projektach

Kluczowe zasady (wydrukuj, przyklej!)

1. Actions = Skutki uboczne, Filters = Transformacje Actions robią coś. Filters zmieniają wartość.

2. ZAWSZE return w filtrach Zapomnienie return = złamana strona.

3. Prefix własne hookimyplugin_action_name nie action_name

4. Bezpieczeństwo przede wszystkim

  • Weryfikacja w save_post
  • Sprawdzenia uprawnień
  • Oczyszczanie/zabezpieczanie wszystkich danych

5. Dokumentuj swoje hooki Inni developerzy będą Ci wdzięczni.

6. Używaj Query Monitor Najszybszy sposób na zrozumienie co się dzieje.

7. Nie modyfikuj core WordPress Zawsze używaj hooków. Zero wyjątków.

8. Testuj po każdej zmianie Szczególnie przy priorytetach i usuwaniu hooków.

Zasoby – gdzie się dalej uczyć

Oficjalna dokumentacja:

Hook odniesienia:

Narzędzia:

  • Query Monitor wtyczka (must-have!)
  • Debug Bar wtyczka
  • WP-CLI (linia poleceń)

Społeczności:

  • WordPress Stack Exchange (Q&A)
  • Advanced WordPress (Facebook group)
  • WPBeginner (tutorials – poradniki)

Ostatnie przemyślenia – myśl jak ekosystem

WordPress Hooks nie są tylko o technicznych umiejętnościach. To myślenie o kodzie jako ekosystemie.

Twój kod nie istnieje w izolacji. Inne wtyczki, motywy, core WordPress – wszystko musi współpracować. Hooki to język tej współpracy.

Dobre praktyki hooków = szacunek dla ekosystemu.

Kiedy tworzysz wtyczkę z własnymi hookami, dajesz innym możliwość rozszerzania Twojego kodu. Zachowujesz możliwość aktualizacji, kiedy używasz hooków zamiast edytować core, Kiedy dokumentujesz swoje hooki, budujesz społeczność.

To jest WordPress way (sposób WordPress).

Architektura sterowana zdarzeniami. Rozszerzalny. Współpracujący. Otwarty.

Master hooki = Master WordPress.

To nie koniec nauki. To początek. Każdy plugin który teraz przeczytasz, będziesz rozumiał głębiej. Każda customizacja będzie łatwiejsza. Każdy problem będzie miał eleganckie rozwiązanie.

Więc otwórz functions.php. Dodaj swój pierwszy hook. I zobacz jak WordPress się otwiera.

Witamy w systemie hooków. Witamy w prawdziwym rozwoju WordPressa.


Masz pytania o WordPress Hooks? Podziel się w komentarzach – jakie hooki używasz najczęściej? Jakie masz wyzwania? Chętnie pomogę i poznam Twoje doświadczenia!

Ten wpis powstał w oparciu o moje wcześniejsze doświadczenie zawodowe jako WordPress Developer. Obecnie nie prowadzę już działalności gospodarczej ani nie świadczę tych usług — treść zostawiam jako źródło wiedzy dla osób, które wciąż się tym zajmują.

Bądź na bieżąco...

otrzymuj najnowsze wiadomości, aktualizacje i wiele innych rzeczy co 2 tygodnie.

Zostaw komentarz

Przewijanie do góry