
🛠 Як створити плагін віджетів для WordPress: покроковий посібник
💡 Як зібрати плагін віджета WordPress
- Створіть папку
my-widget-pluginуwp-content/plugins/і файлmy-widget-plugin.phpіз заголовком плагіна, потім активуйте його в адмінці - Оголосіть клас, що успадковує
WP_Widget, і перевизначте методи__construct(),form(),update()таwidget() - Зареєструйте віджет функцією
register_widget()на хукуwidgets_init, щоб WordPress побачив його в списку - У методі
form()виведіть поля налаштувань, вupdate()очистіть і збережіть дані черезsanitize_text_field() - У методі
widget()виведіть вміст на сайті, екрануючи значення черезesc_html()таwp_kses_post()
Крок 1: створюємо плагін-каркас
Віджет у WordPress — це не просто рядок у functions.php. Якщо ви хочете, щоб налаштування зберігалися, форма в адмінці працювала, а сам віджет не ламався під час наступного оновлення теми, його потрібно оформити як плагін. Це ізолює код і робить віджет незалежним від зміни теми.
Почніть із порожньої папки. Перейдіть у wp-content/plugins/ і створіть директорію my-widget-plugin. Усередині, файл my-widget-plugin.php. Саме його WordPress прочитає першим під час активації. Відкрийте файл і додайте стандартний заголовок плагіна:
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 */
Збережіть. Тепер зайдіть в адмінку WordPress → Плагіни. Якщо плагін з’явився в списку, каркас готовий. Натисніть «Активувати». Поки він не робить нічого корисного, ми лише заявили про його існування. Але WordPress уже знає, що такий плагін є, і готовий виконувати його код. Це важливий принцип: спочатку реєстрація, потім логіка.
Як влаштований плагін зсередини, які хуки спрацьовують під час активації та як WordPress знаходить ваш файл, дивіться у відео вище. А ми переходимо до найцікавішого: класу віджета.
Крок 2: реєструємо віджет через WP_Widget
WordPress надає готовий клас WP_Widget, він перебуває в ядрі з версії 2.8 і відтоді залишається основою для будь-яких кастомних віджетів. Вам не потрібно писати логіку збереження, генерації полів і реєстрації з нуля, достатньо успадкуватися та перевизначити чотири методи.
Додайте цей код у ваш my-widget-plugin.php одразу після заголовка плагіна, перед закриваючим ?>:
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' );
Розберемо, що тут відбувається. Клас My_Custom_Widget успадковує WP_Widget, це дає вам готові методи get_field_id() та get_field_name() для генерації атрибутів полів форми. У конструкторі ми передаємо батьківському класу три речі: унікальний ID віджета (латиницею, малими літерами, без пробілів, my_custom_widget), його зрозуміле людині ім’я (функція __() робить його перекладним) і масив опцій. Параметр customize_selective_refresh => true дозволяє оновлювати віджет у кастомайзері без перезавантаження всієї сторінки, дрібниця, яка економить нерви під час налаштування.
Функція my_register_custom_widget() викликає register_widget() на хуку widgets_init. Саме так WordPress дізнається про існування вашого віджета. Без цього рядка в адмінці нічого не з’явиться.
Тепер наповнимо методи form(), update() і widget() реальною логікою.
Крок 3: створюємо форму віджета в адмінці
Форма — це те, що бачить адміністратор, коли перетягує віджет у сайдбар. Вона складається з полів: текстових, випадних списків, чекбоксів. Кожне поле повинно вміти зберігати значення та показувати поточне під час повторного відкриття.
3.1. Функція form(), поля введення
Додайте цей код у метод form() вашого класу. Він створює п’ять полів: заголовок, текстовий рядок, текстова область, чекбокс і випадний список.
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 }
Зверніть увагу на дві речі. Перша: ми замінили застарілу функцію extract() на пряме звернення до елементів масиву. extract() давно виключено зі стандартів кодування WordPress, він створює змінні з іменами ключів, що небезпечно та ускладнює налагодження. Друга: кожне виведене значення пропущено через esc_attr(), wp_kses_post() або esc_html(). Це не параноя, дані з бази можуть надійти звідки завгодно, і санітизація обов’язкова.
3.2. Функція update(), збереження
Метод update() викликається під час натискання кнопки «Зберегти» у формі віджета. Його завдання, перевірити кожне поле та повернути очищений масив для запису в базу.
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 }
Тут ми використовуємо sanitize_text_field() замість wp_strip_all_tags(), ця функція додатково прибирає пробільні символи та приводить рядок до безпечного вигляду. Для текстової області залишаємо wp_kses_post(): вона дозволяє базовий HTML (посилання, жирний шрифт, списки), але вирізає скрипти. Чекбокс повертає 1 або false, у базі це буде читабельно й однозначно.
Крок 4: виводимо віджет на сайті
Функція widget() — це те, що побачить відвідувач. Вона отримує два параметри: $args (обгортка віджета, теги до і після заголовка, сайдбара) та $instance (збережені налаштування конкретного екземпляра).
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 }
Ключовий момент: ми замінили extract( $args ) на пряме звернення $args['before_widget']. Причина та сама, extract() застарів і небезпечний. Також ми обгорнули вивід в esc_html() там, де очікується чистий текст: заголовок, текстовий рядок, значення селекта. Текстова область виводиться через wp_kses_post(), якщо адміністратор вставив посилання або жирний шрифт, вони збережуться.
CSS-клас wp_widget_plugin_box дозволяє стилізувати блок із таблиці стилів теми. Змінюйте його на свій, головне, щоб клас був унікальним і не конфліктував із класами теми.
Повний код плагіна
Зберемо все разом. Ось готовий файл my-widget-plugin.php, який можна скопіювати й активувати:
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' );
Помістіть цей файл у wp-content/plugins/my-widget-plugin/, активуйте плагін і перетягніть віджет у будь-який сайдбар через Зовнішній вигляд → Віджети. Заповніть поля, збережіть і перевірте сайт.
Готовий код також доступний на GitHub: wpexplorer/my-widget-plugin, там же можна порівняти з оригінальною версією 2017 року та побачити, що саме ми змінили.
⁉️🤔 Часті запитання
Навіщо виносити віджет у плагін, якщо можна додати код у functions.php теми?
Код у functions.php прив’язаний до активної теми. Змінили тему, віджет зник. Плагін же працює незалежно від теми. Плюс плагін можна активувати вибірково на різних сайтах, а код теми, ні. Якщо віджет вирішує бізнес-задачу (наприклад, виводить форму підписки з конкретним верстанням), його місце в плагіні.
Чим sanitize_text_field() кращий за wp_strip_all_tags()?
sanitize_text_field()не лише прибирає HTML-теги, а й нормалізує пробіли, видаляє невидимі керувальні символи та приводить рядок до UTF-8. Це більш повне очищення.wp_strip_all_tags()залишає «сирий» текст з усіма пробільними артефактами. Для текстових полів форми завжди вибирайтеsanitize_text_field().
Чому ви замінили extract() на пряме звернення до масиву?
Функцію
extract()виключено зі стандартів кодування WordPress з версії 4.3. Вона створює змінні з іменами ключів масиву в локальній області видимості, якщо ключ збігається з наявною змінною, ви перезапишете її й отримаєте важковловимий баг. Пряме звернення$args['before_widget']читабельне й безпечне.
Чи потрібно підтримувати блокові віджети (Gutenberg)?
Класичний
WP_Widgetпрацює з блоковими сайдбарами через зворотну сумісність, WordPress сам обгортає його в Legacy Widget Block. Цього достатньо для більшості сценаріїв. Якщо ви хочете створювати саме нативні блоки, дивіться Block Editor Handbook, там окремий API. Але стартувати простіше зWP_Widget: код коротший, налагодження швидше, і він працює на всіх версіях WordPress без плагінів-милиць.
Як налагоджувати віджет, якщо він не з’являється в адмінці?
Перевірте три речі. Перше: хук
widgets_initвідпрацював? Додайтеerror_log( 'Widget registered' )у функцію реєстрації та подивіться логи. Друге: чи немає фатальної помилки в конструкторі? УвімкнітьWP_DEBUGу wp-config.php. Третє: ім’я класу вregister_widget()збігається з іменем класу-нащадкаWP_Widget? Одрук в одній літері, і віджета немає.
Що далі: від шаблону до свого віджета
Ми зібрали робочий плагін із п’ятьма типами полів. Це не кінцевий продукт, а каркас: беріть його за основу та змінюйте під свою задачу. Потрібен віджет для форми підписки, замініть текстові поля на поля email та імені, додайте виклик API розсилки у widget(). Потрібен блок з акціями та банерами, завантажуйте зображення через media uploader і виводьте у верстці. Механіка одна й та сама: form() малює поля, update() зберігає, widget() виводить.
Почніть із малого: скопіюйте повний код вище, активуйте на тестовому сайті та пограйтеся з налаштуваннями. Коли зрозумієте, як дані течуть від форми до фронтенду, додавайте свої поля. А якщо віджет упав із білим екраном, повертайтеся до кроку 2 і перевіряйте конструктор.



