Skip to content

Wszystko o WordPressie, tworzeniu stron — i nie tylko

🛠 Jak stworzyć wtyczkę widżetów dla WordPress: przewodnik krok po kroku

🛠 Jak stworzyć wtyczkę widżetów dla WordPress: przewodnik krok po kroku

💡 Jak zbudować wtyczkę widżetu WordPress

  • Utwórz folder my-widget-plugin w wp-content/plugins/ i plik my-widget-plugin.php z nagłówkiem wtyczki, następnie aktywuj ją w panelu administracyjnym
  • Zadeklaruj klasę dziedziczącą po WP_Widget i nadpisz metody __construct(), form(), update() oraz widget()
  • Zarejestruj widżet funkcją register_widget() na haku widgets_init, aby WordPress zobaczył go na liście
  • W metodzie form() wyświetl pola ustawień, w update() wyczyść i zapisz dane przez sanitize_text_field()
  • W metodzie widget() wyświetl zawartość na stronie, escapując wartości przez esc_html() oraz wp_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/*
3Plugin Name: My Widget Plugin
4Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/
5Description: Добавляет настраиваемый виджет с текстом, текстовой областью, чекбоксом и выпадающим списком.
6Version: 1.0
7Author: AJ Clarke
8Author URI: https://www.wpexplorer.com/
9License: 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// Класс виджета
2class 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// Регистрация виджета
28function my_register_custom_widget() {
29 register_widget( 'My_Custom_Widget' );
30}
31add_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ą.

1public 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.

1public 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).

1public 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/*
3Plugin Name: My Widget Plugin
4Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/
5Description: Добавляет настраиваемый виджет с текстом, текстовой областью, чекбоксом и выпадающим списком.
6Version: 1.0
7Author: AJ Clarke
8Author URI: https://www.wpexplorer.com/
9License: GPL2
10*/
11
12class 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
144function my_register_custom_widget() {
145 register_widget( 'My_Custom_Widget' );
146}
147add_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_Widget dział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 z WP_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_init zadział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_DEBUG w wp-config.php. Trzecie: czy nazwa klasy w register_widget() pokrywa się z nazwą klasy potomnej WP_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.