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 hookiPrzykł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 niebody_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_name2. 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żytkownikaFilters – 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 logowaniaAction plan – ucz się praktycznie
Podstawy – Tydzień 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)
Praktyka – Kolejny 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:
- Plugin Handbook: https://developer.wordpress.org/plugins/hooks/
- Theme Handbook: https://developer.wordpress.org/themes/basics/theme-functions/
- Code Reference: https://developer.wordpress.org/reference/
Hook odniesienia:
- Adam Brown’s Hook Reference: http://adambrown.info/p/wp_hooks
- WP Hooks: https://wphooks.org/
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!


