
🛠 Jak stworzyć wtyczkę widżetów dla WordPress: przewodnik krok po kroku
💡 Jak zbudować wtyczkę widżetu WordPress
- Utwórz folder
my-widget-pluginwwp-content/plugins/i plikmy-widget-plugin.phpz nagłówkiem wtyczki, następnie aktywuj ją w panelu administracyjnym - Zadeklaruj klasę dziedziczącą po
WP_Widgeti nadpisz metody__construct(),form(),update()orazwidget() - Zarejestruj widżet funkcją
register_widget()na hakuwidgets_init, aby WordPress zobaczył go na liście - W metodzie
form()wyświetl pola ustawień, wupdate()wyczyść i zapisz dane przezsanitize_text_field() - W metodzie
widget()wyświetl zawartość na stronie, escapując wartości przezesc_html()orazwp_kses_post()
Krok 1: tworzymy szkielet wtyczki
Widżet w WordPressie to nie tylko linijka w functions.php. Jeśli chce Pan/Pani, aby ustawienia się zapisywały, formularz w panelu administracyjnym działał, a sam widżet nie zepsuł się przy kolejnej aktualizacji szablonu, należy go zbudować jako wtyczkę. Izoluje to kod i uniezależnia widżet od zmiany szablonu.
Proszę zacząć od pustego folderu. Proszę przejść do wp-content/plugins/ i utworzyć katalog my-widget-plugin. Wewnątrz plik my-widget-plugin.php. To właśnie go WordPress odczyta jako pierwszy podczas aktywacji. Proszę otworzyć plik i dodać standardowy nagłówek wtyczki:
1 <?php 2 /* 3 Plugin Name: My Widget Plugin 4 Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/ 5 Description: Добавляет настраиваемый виджет с текстом, текстовой областью, чекбоксом и выпадающим списком. 6 Version: 1.0 7 Author: AJ Clarke 8 Author URI: https://www.wpexplorer.com/ 9 License: GPL2 10 */
Proszę zapisać. Teraz proszę wejść do panelu administracyjnego WordPress → Wtyczki. Jeśli wtyczka pojawiła się na liście, szkielet jest gotowy. Proszę kliknąć „Aktywuj". Na razie nie robi nic użytecznego, jedynie zadeklarowaliśmy jej istnienie. Ale WordPress już wie, że taka wtyczka istnieje i jest gotowy wykonywać jej kod. To ważna zasada: najpierw rejestracja, potem logika.
Jak wtyczka jest zbudowana od środka, które haki są uruchamiane podczas aktywacji i jak WordPress znajduje Pana/Pani plik, można zobaczyć na filmie powyżej. A my przechodzimy do najciekawszego: klasy widżetu.
Krok 2: rejestrujemy widżet przez WP_Widget
WordPress udostępnia gotową klasę WP_Widget, znajduje się ona w jądrze od wersji 2.8 i od tego czasu pozostaje podstawą dla wszelkich niestandardowych widżetów. Nie musi Pan/Pani pisać logiki zapisywania, generowania pól i rejestracji od zera, wystarczy odziedziczyć i nadpisać cztery metody.
Proszę dodać ten kod do Pana/Pani pliku my-widget-plugin.php zaraz po nagłówku wtyczki, przed zamykającym ?>:
1 // Класс виджета 2 class My_Custom_Widget extends WP_Widget { 3 4 public function __construct() { 5 parent::__construct( 6 'my_custom_widget', 7 __( 'My Custom Widget', 'text_domain' ), 8 array( 9 'customize_selective_refresh' => true, 10 ) 11 ); 12 } 13 14 public function form( $instance ) { 15 /* ... форма в админке ... */ 16 } 17 18 public function update( $new_instance, $old_instance ) { 19 /* ... сохранение настроек ... */ 20 } 21 22 public function widget( $args, $instance ) { 23 /* ... вывод на сайте ... */ 24 } 25 } 26 27 // Регистрация виджета 28 function my_register_custom_widget() { 29 register_widget( 'My_Custom_Widget' ); 30 } 31 add_action( 'widgets_init', 'my_register_custom_widget' );
Przeanalizujmy, co tu się dzieje. Klasa My_Custom_Widget dziedziczy WP_Widget, co daje Panu/Pani gotowe metody get_field_id() oraz get_field_name() do generowania atrybutów pól formularza. W konstruktorze przekazujemy do klasy nadrzędnej trzy rzeczy: unikalny ID widżetu (łacińskimi literami, małymi, bez spacji, my_custom_widget), jego czytelną dla człowieka nazwę (funkcja __() czyni ją przetłumaczalną) oraz tablicę opcji. Parametr customize_selective_refresh => true pozwala aktualizować widżet w customizerze bez przeładowywania całej strony, drobiazg, który oszczędza nerwy podczas konfiguracji.
Funkcja my_register_custom_widget() wywołuje register_widget() na haku widgets_init. Właśnie w ten sposób WordPress dowiaduje się o istnieniu Pana/Pani widżetu. Bez tej linijki nic nie pojawi się w panelu administracyjnym.
Teraz wypełnimy metody form(), update() i widget() rzeczywistą logiką.
Krok 3: tworzymy formularz widżetu w panelu administracyjnym
Formularz to to, co widzi administrator, kiedy przeciąga widżet do sidebaru. Składa się z pól: tekstowych, list rozwijanych, checkboxów. Każde pole musi umieć zapisać wartość i pokazać bieżącą przy ponownym otwarciu.
3.1. Funkcja form(), pola wejściowe
Proszę dodać ten kod do metody form() Pana/Pani klasy. Tworzy on pięć pól: tytuł, wiersz tekstowy, pole tekstowe, checkbox i listę rozwijaną.
1 public function form( $instance ) { 2 3 $defaults = array( 4 'title' => '', 5 'text' => '', 6 'textarea' => '', 7 'checkbox' => '', 8 'select' => '', 9 ); 10 11 $args = wp_parse_args( (array) $instance, $defaults ); 12 $title = $args['title']; 13 $text = $args['text']; 14 $textarea = $args['textarea']; 15 $checkbox = $args['checkbox']; 16 $select = $args['select']; 17 ?> 18 19 <p> 20 <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"> 21 <?php _e( 'Заголовок виджета', 'text_domain' ); ?> 22 </label> 23 <input class="widefat" 24 id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" 25 name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" 26 type="text" 27 value="<?php echo esc_attr( $title ); ?>" /> 28 </p> 29 30 <p> 31 <label for="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>"> 32 <?php _e( 'Текст:', 'text_domain' ); ?> 33 </label> 34 <input class="widefat" 35 id="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>" 36 name="<?php echo esc_attr( $this->get_field_name( 'text' ) ); ?>" 37 type="text" 38 value="<?php echo esc_attr( $text ); ?>" /> 39 </p> 40 41 <p> 42 <label for="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>"> 43 <?php _e( 'Текстовая область:', 'text_domain' ); ?> 44 </label> 45 <textarea class="widefat" 46 id="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>" 47 name="<?php echo esc_attr( $this->get_field_name( 'textarea' ) ); ?>"><?php 48 echo wp_kses_post( $textarea ); 49 ?></textarea> 50 </p> 51 52 <p> 53 <input id="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>" 54 name="<?php echo esc_attr( $this->get_field_name( 'checkbox' ) ); ?>" 55 type="checkbox" 56 value="1" 57 <?php checked( '1', $checkbox ); ?> /> 58 <label for="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>"> 59 <?php _e( 'Показать дополнительный блок', 'text_domain' ); ?> 60 </label> 61 </p> 62 63 <p> 64 <label for="<?php echo $this->get_field_id( 'select' ); ?>"> 65 <?php _e( 'Вариант отображения', 'text_domain' ); ?> 66 </label> 67 <select name="<?php echo $this->get_field_name( 'select' ); ?>" 68 id="<?php echo $this->get_field_id( 'select' ); ?>" 69 class="widefat"> 70 <?php 71 $options = array( 72 '' => __( '— Выберите —', 'text_domain' ), 73 'option_1' => __( 'Вариант 1', 'text_domain' ), 74 'option_2' => __( 'Вариант 2', 'text_domain' ), 75 'option_3' => __( 'Вариант 3', 'text_domain' ), 76 ); 77 foreach ( $options as $key => $name ) { 78 printf( 79 '<option value="%s" %s>%s</option>', 80 esc_attr( $key ), 81 selected( $select, $key, false ), 82 esc_html( $name ) 83 ); 84 } 85 ?> 86 </select> 87 </p> 88 89 <?php }
Proszę zwrócić uwagę na dwie rzeczy. Pierwsza: zastąpiliśmy przestarzałą funkcję extract() bezpośrednim odwołaniem do elementów tablicy. extract() został dawno wykluczony ze standardów kodowania WordPressa, tworzy zmienne o nazwach kluczy, co jest niebezpieczne i utrudnia debugowanie. Druga: każda wyświetlona wartość została przepuszczona przez esc_attr(), wp_kses_post() lub esc_html(). To nie paranoja, dane z bazy mogą pochodzić z dowolnego źródła, a sanityzacja jest obowiązkowa.
3.2. Funkcja update(), zapisywanie
Metoda update() jest wywoływana po kliknięciu przycisku „Zapisz" w formularzu widżetu. Jej zadaniem jest sprawdzenie każdego pola i zwrócenie oczyszczonej tablicy do zapisu w bazie danych.
1 public function update( $new_instance, $old_instance ) { 2 $instance = $old_instance; 3 4 $instance['title'] = isset( $new_instance['title'] ) 5 ? sanitize_text_field( $new_instance['title'] ) : ''; 6 $instance['text'] = isset( $new_instance['text'] ) 7 ? sanitize_text_field( $new_instance['text'] ) : ''; 8 $instance['textarea'] = isset( $new_instance['textarea'] ) 9 ? wp_kses_post( $new_instance['textarea'] ) : ''; 10 $instance['checkbox'] = isset( $new_instance['checkbox'] ) ? 1 : false; 11 $instance['select'] = isset( $new_instance['select'] ) 12 ? sanitize_text_field( $new_instance['select'] ) : ''; 13 14 return $instance; 15 }
Tutaj używamy sanitize_text_field() zamiast wp_strip_all_tags(), ta funkcja dodatkowo usuwa białe znaki i sprowadza ciąg do bezpiecznej postaci. Dla pola tekstowego pozostawiamy wp_kses_post(): pozwala ono na podstawowy HTML (linki, pogrubienie, listy), ale wycina skrypty. Checkbox zwraca 1 lub false, w bazie będzie to czytelne i jednoznaczne.
Krok 4: wyświetlamy widżet na stronie
Funkcja widget() to to, co zobaczy odwiedzający. Otrzymuje ona dwa parametry: $args (opakowanie widżetu, tagi przed i po tytule, sidebaru) oraz $instance (zapisane ustawienia konkretnej instancji).
1 public function widget( $args, $instance ) { 2 3 $title = isset( $instance['title'] ) 4 ? apply_filters( 'widget_title', $instance['title'] ) : ''; 5 $text = isset( $instance['text'] ) ? $instance['text'] : ''; 6 $textarea = isset( $instance['textarea'] ) ? $instance['textarea'] : ''; 7 $select = isset( $instance['select'] ) ? $instance['select'] : ''; 8 $checkbox = ! empty( $instance['checkbox'] ) ? $instance['checkbox'] : false; 9 10 echo $args['before_widget']; 11 12 echo '<div class="widget-text wp_widget_plugin_box">'; 13 14 if ( $title ) { 15 echo $args['before_title'] . esc_html( $title ) . $args['after_title']; 16 } 17 18 if ( $text ) { 19 echo '<p>' . esc_html( $text ) . '</p>'; 20 } 21 22 if ( $textarea ) { 23 echo '<div class="widget-textarea">' . wp_kses_post( $textarea ) . '</div>'; 24 } 25 26 if ( $select ) { 27 echo '<p class="widget-select">' . esc_html( $select ) . '</p>'; 28 } 29 30 if ( $checkbox ) { 31 echo '<p class="widget-checkbox-result">' . esc_html__( 'Дополнительный блок активирован', 'text_domain' ) . '</p>'; 32 } 33 34 echo '</div>'; 35 36 echo $args['after_widget']; 37 }
Kluczowy moment: zastąpiliśmy extract( $args ) bezpośrednim odwołaniem $args['before_widget']. Powód jest ten sam, extract() jest przestarzały i niebezpieczny. Dodatkowo opakowaliśmy wyjście w esc_html() tam, gdzie oczekiwany jest czysty tekst: tytuł, wiersz tekstowy, wartość selecta. Pole tekstowe jest wyświetlane przez wp_kses_post(), jeśli administrator wstawił link lub pogrubienie, zostaną one zachowane.
Klasa CSS wp_widget_plugin_box pozwala stylizować blok z arkusza stylów szablonu. Proszę zmienić ją na swoją, ważne, aby klasa była unikalna i nie kolidowała z klasami szablonu.
Pełny kod wtyczki
Zbierzmy wszystko razem. Oto gotowy plik my-widget-plugin.php, który można skopiować i aktywować:
1 <?php 2 /* 3 Plugin Name: My Widget Plugin 4 Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/ 5 Description: Добавляет настраиваемый виджет с текстом, текстовой областью, чекбоксом и выпадающим списком. 6 Version: 1.0 7 Author: AJ Clarke 8 Author URI: https://www.wpexplorer.com/ 9 License: GPL2 10 */ 11 12 class My_Custom_Widget extends WP_Widget { 13 14 public function __construct() { 15 parent::__construct( 16 'my_custom_widget', 17 __( 'My Custom Widget', 'text_domain' ), 18 array( 'customize_selective_refresh' => true ) 19 ); 20 } 21 22 public function form( $instance ) { 23 $defaults = array( 24 'title' => '', 'text' => '', 'textarea' => '', 25 'checkbox' => '', 'select' => '' 26 ); 27 $args = wp_parse_args( (array) $instance, $defaults ); 28 $title = $args['title']; 29 $text = $args['text']; 30 $textarea = $args['textarea']; 31 $checkbox = $args['checkbox']; 32 $select = $args['select']; 33 ?> 34 <p> 35 <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"> 36 <?php _e( 'Заголовок виджета', 'text_domain' ); ?> 37 </label> 38 <input class="widefat" 39 id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" 40 name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" 41 type="text" value="<?php echo esc_attr( $title ); ?>" /> 42 </p> 43 <p> 44 <label for="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>"> 45 <?php _e( 'Текст:', 'text_domain' ); ?> 46 </label> 47 <input class="widefat" 48 id="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>" 49 name="<?php echo esc_attr( $this->get_field_name( 'text' ) ); ?>" 50 type="text" value="<?php echo esc_attr( $text ); ?>" /> 51 </p> 52 <p> 53 <label for="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>"> 54 <?php _e( 'Текстовая область:', 'text_domain' ); ?> 55 </label> 56 <textarea class="widefat" 57 id="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>" 58 name="<?php echo esc_attr( $this->get_field_name( 'textarea' ) ); ?>"><?php 59 echo wp_kses_post( $textarea ); 60 ?></textarea> 61 </p> 62 <p> 63 <input id="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>" 64 name="<?php echo esc_attr( $this->get_field_name( 'checkbox' ) ); ?>" 65 type="checkbox" value="1" <?php checked( '1', $checkbox ); ?> /> 66 <label for="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>"> 67 <?php _e( 'Показать дополнительный блок', 'text_domain' ); ?> 68 </label> 69 </p> 70 <p> 71 <label for="<?php echo $this->get_field_id( 'select' ); ?>"> 72 <?php _e( 'Вариант отображения', 'text_domain' ); ?> 73 </label> 74 <select name="<?php echo $this->get_field_name( 'select' ); ?>" 75 id="<?php echo $this->get_field_id( 'select' ); ?>" class="widefat"> 76 <?php 77 $options = array( 78 '' => __( '— Выберите —', 'text_domain' ), 79 'option_1' => __( 'Вариант 1', 'text_domain' ), 80 'option_2' => __( 'Вариант 2', 'text_domain' ), 81 'option_3' => __( 'Вариант 3', 'text_domain' ), 82 ); 83 foreach ( $options as $key => $name ) { 84 printf( 85 '<option value="%s" %s>%s</option>', 86 esc_attr( $key ), 87 selected( $select, $key, false ), 88 esc_html( $name ) 89 ); 90 } 91 ?> 92 </select> 93 </p> 94 <?php 95 } 96 97 public function update( $new_instance, $old_instance ) { 98 $instance = $old_instance; 99 $instance['title'] = isset( $new_instance['title'] ) 100 ? sanitize_text_field( $new_instance['title'] ) : ''; 101 $instance['text'] = isset( $new_instance['text'] ) 102 ? sanitize_text_field( $new_instance['text'] ) : ''; 103 $instance['textarea'] = isset( $new_instance['textarea'] ) 104 ? wp_kses_post( $new_instance['textarea'] ) : ''; 105 $instance['checkbox'] = isset( $new_instance['checkbox'] ) ? 1 : false; 106 $instance['select'] = isset( $new_instance['select'] ) 107 ? sanitize_text_field( $new_instance['select'] ) : ''; 108 return $instance; 109 } 110 111 public function widget( $args, $instance ) { 112 $title = isset( $instance['title'] ) 113 ? apply_filters( 'widget_title', $instance['title'] ) : ''; 114 $text = isset( $instance['text'] ) ? $instance['text'] : ''; 115 $textarea = isset( $instance['textarea'] ) ? $instance['textarea'] : ''; 116 $select = isset( $instance['select'] ) ? $instance['select'] : ''; 117 $checkbox = ! empty( $instance['checkbox'] ) ? $instance['checkbox'] : false; 118 119 echo $args['before_widget']; 120 echo '<div class="widget-text wp_widget_plugin_box">'; 121 122 if ( $title ) { 123 echo $args['before_title'] . esc_html( $title ) . $args['after_title']; 124 } 125 if ( $text ) { 126 echo '<p>' . esc_html( $text ) . '</p>'; 127 } 128 if ( $textarea ) { 129 echo '<div class="widget-textarea">' . wp_kses_post( $textarea ) . '</div>'; 130 } 131 if ( $select ) { 132 echo '<p class="widget-select">' . esc_html( $select ) . '</p>'; 133 } 134 if ( $checkbox ) { 135 echo '<p class="widget-checkbox-result">' 136 . esc_html__( 'Дополнительный блок активирован', 'text_domain' ) . '</p>'; 137 } 138 139 echo '</div>'; 140 echo $args['after_widget']; 141 } 142 } 143 144 function my_register_custom_widget() { 145 register_widget( 'My_Custom_Widget' ); 146 } 147 add_action( 'widgets_init', 'my_register_custom_widget' );
Proszę umieścić ten plik w wp-content/plugins/my-widget-plugin/, aktywować wtyczkę i przeciągnąć widżet do dowolnego sidebaru przez Wygląd → Widżety. Proszę wypełnić pola, zapisać i sprawdzić stronę.
Gotowy kod jest również dostępny na GitHub: wpexplorer/my-widget-plugin, tam też można porównać z oryginalną wersją z 2017 roku i zobaczyć, co dokładnie zmieniliśmy.
⁉️🤔 Często zadawane pytania
Po co wydzielać widżet do wtyczki, skoro można dodać kod w functions.php szablonu?
Kod w functions.php jest powiązany z aktywnym szablonem. Zmiana szablonu, widżet znika. Wtyczka natomiast działa niezależnie od szablonu. Dodatkowo wtyczkę można aktywować wybiórczo na różnych stronach, a kodu szablonu nie. Jeśli widżet rozwiązuje zadanie biznesowe (na przykład wyświetla formularz subskrypcji z konkretnym układem), jego miejsce jest we wtyczce.
Czym sanitize_text_field() jest lepsze od wp_strip_all_tags()?
sanitize_text_field()nie tylko usuwa tagi HTML, ale także normalizuje spacje, usuwa niewidoczne znaki sterujące i konwertuje ciąg do UTF-8. To pełniejsze czyszczenie.wp_strip_all_tags()pozostawia „surowy" tekst ze wszystkimi artefaktami białych znaków. Dla pól tekstowych formularza zawsze należy wybieraćsanitize_text_field().
Dlaczego zastąpili Państwo extract() bezpośrednim odwołaniem do tablicy?
Funkcja
extract()została wykluczona ze standardów kodowania WordPress od wersji 4.3. Tworzy ona zmienne o nazwach kluczy tablicy w lokalnym zakresie widoczności, jeśli klucz pokrywa się z istniejącą zmienną, nadpisze ją Pan/Pani i otrzyma trudny do wykrycia błąd. Bezpośrednie odwołanie$args['before_widget']jest czytelne i bezpieczne.
Czy trzeba wspierać widżety blokowe (Gutenberg)?
Klasyczny
WP_Widgetdziała z sidebarami blokowymi poprzez kompatybilność wsteczną, WordPress sam opakowuje go w Legacy Widget Block. To wystarcza dla większości scenariuszy. Jeśli chce Pan/Pani tworzyć natywne bloki, proszę zajrzeć do Block Editor Handbook, tam jest osobne API. Ale startować łatwiej zWP_Widget: kod jest krótszy, debugowanie szybsze i działa na wszystkich wersjach WordPressa bez wtyczek-podpórek.
Jak debugować widżet, jeśli nie pojawia się w panelu administracyjnym?
Proszę sprawdzić trzy rzeczy. Pierwsze: czy hak
widgets_initzadziałał? Proszę dodaćerror_log( 'Widget registered' )w funkcji rejestracji i sprawdzić logi. Drugie: czy nie ma fatalnego błędu w konstruktorze? Proszę włączyćWP_DEBUGw wp-config.php. Trzecie: czy nazwa klasy wregister_widget()pokrywa się z nazwą klasy potomnejWP_Widget? Literówka w jednej literze i widżetu nie ma.
Co dalej: od szablonu do własnego widżetu
Zbudowaliśmy działającą wtyczkę z pięcioma typami pól. To nie jest produkt końcowy, a szkielet: proszę wziąć go za podstawę i zmieniać pod swoje zadanie. Jeśli potrzebny jest widżet dla formularza subskrypcji, proszę zastąpić pola tekstowe polami email i imienia, dodać wywołanie API newslettera w widget(). Jeśli potrzebny jest blok z akcjami i banerami, proszę ładować obrazy przez media uploader i wyświetlać w układzie. Mechanika jest ta sama: form() rysuje pola, update() zapisuje, widget() wyświetla.
Proszę zacząć od małego: skopiować pełny kod powyżej, aktywować na testowej stronie i pobawić się ustawieniami. Kiedy zrozumie Pan/Pani, jak dane płyną od formularza do frontendu, proszę dodawać swoje pola. A jeśli widżet upadł z białym ekranem, proszę wrócić do kroku 2 i sprawdzić konstruktor.



