API Сайдбаров и Виджетов
В техническом плане виджет WordPress - это PHP-объект, который наследует класс WP_Widget и выводит HTML через метод widget().
Widgets API используется для двух связанных задач:
- создания новых виджетов;
- регистрации областей виджетов, в которые эти виджеты можно добавить.
Код Widgets API находится в файлах:
Термины
Виджет - отдельный PHP-объект, который выводит HTML и может иметь собственные настройки.
Область виджетов или сайдбар - зарегистрированная область темы, куда пользователь может добавлять виджеты через админку WordPress.
Термин sidebar в API не обязательно означает боковую колонку. Это может быть любая область: футер, хедер, блок под записью, рекламная зона и т.д.
Разработка виджетов
Чтобы создать виджет, нужно расширить стандартный класс WP_Widget и переопределить нужные методы.
Минимально обязательный метод - widget(). Если у виджета есть настройки, также нужно переопределить методы form() и update().
class My_Widget extends WP_Widget {
public function __construct() {
$widget_ops = array(
'classname' => 'my_widget',
'description' => 'Описание виджета.',
);
parent::__construct( 'my_widget', 'My Widget', $widget_ops );
}
public function widget( $args, $instance ) {
// Вывод виджета на фронте.
}
public function form( $instance ) {
// Форма настроек виджета в админке.
}
public function update( $new_instance, $old_instance ) {
// Обработка и очистка настроек перед сохранением.
}
}
Затем виджет нужно зарегистрировать на хуке widgets_init:
add_action( 'widgets_init', 'register_my_widget' );
function register_my_widget() {
register_widget( 'My_Widget' );
}
Методы класса WP_Widget
__construct()
Регистрирует тип виджета и передает данные родительскому классу.
parent::__construct( $id_base, $name, $widget_options, $control_options );
Параметры:
$id_base- базовый ID виджета. Используется в HTML, опциях и ID экземпляров виджета.$name- название виджета в интерфейсе админки.$widget_options- массив настроек виджета.$control_options- массив настроек формы управления виджетом.
Пример:
public function __construct() {
$widget_ops = array(
'classname' => 'foo_widget',
'description' => __( 'Displays a custom text block.', 'textdomain' ),
'customize_selective_refresh' => true,
);
parent::__construct(
'foo_widget',
__( 'Foo Widget', 'textdomain' ),
$widget_ops
);
}
widget()
Выводит HTML виджета на фронте.
public function widget( $args, $instance ) {}
Параметры:
$args- аргументы области виджетов:before_widget,after_widget,before_title,after_title.$instance- настройки конкретного экземпляра виджета.
Метод должен выводить результат через echo.
public function widget( $args, $instance ) {
$title = ! empty( $instance['title'] ) ? $instance['title'] : '';
echo $args['before_widget'];
if ( $title ) {
echo $args['before_title'] . esc_html( $title ) . $args['after_title'];
}
echo '<p>' . esc_html__( 'Hello, World!', 'textdomain' ) . '</p>';
echo $args['after_widget'];
}
form()
Выводит форму настроек виджета в админке.
public function form( $instance ) {}
Параметры:
$instance- текущие сохраненные настройки виджета.
В полях формы нужно использовать методы:
$this->get_field_id( 'field_name' )- создает уникальныйidполя;$this->get_field_name( 'field_name' )- создает правильныйnameполя для сохранения.
public function form( $instance ) {
$title = isset( $instance['title'] ) ? $instance['title'] : '';
?>
<p>
<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
<?php esc_html_e( 'Title:', 'textdomain' ); ?>
</label>
<input
class="widefat"
id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
type="text"
value="<?php echo esc_attr( $title ); ?>"
>
</p>
<?php
}
Метод get_field_name() поддерживает имена в формате массива:
$this->get_field_name( 'links[0][url]' );
update()
Обрабатывает настройки виджета перед сохранением.
public function update( $new_instance, $old_instance ) {}
Параметры:
$new_instance- новые данные из формы.$old_instance- старые сохраненные данные.
Метод должен вернуть массив данных, которые нужно сохранить. Если вернуть false, настройки не будут сохранены.
public function update( $new_instance, $old_instance ) {
$instance = array();
$instance['title'] = isset( $new_instance['title'] )
? sanitize_text_field( $new_instance['title'] )
: '';
return $instance;
}
Полный пример виджета
Этот пример создает виджет Foo_Widget с настройкой заголовка и текста.
<?php
class Foo_Widget extends WP_Widget {
public function __construct() {
$widget_ops = array(
'classname' => 'foo_widget',
'description' => __( 'Displays custom text.', 'textdomain' ),
'customize_selective_refresh' => true,
);
parent::__construct(
'foo_widget',
__( 'Foo Widget', 'textdomain' ),
$widget_ops
);
}
public function widget( $args, $instance ) {
$title = isset( $instance['title'] ) ? $instance['title'] : '';
$text = isset( $instance['text'] ) ? $instance['text'] : '';
echo $args['before_widget'];
if ( $title ) {
$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
echo $args['before_title'] . esc_html( $title ) . $args['after_title'];
}
if ( $text ) {
echo '<div class="foo-widget__text">' . wp_kses_post( wpautop( $text ) ) . '</div>';
}
echo $args['after_widget'];
}
public function form( $instance ) {
$title = isset( $instance['title'] ) ? $instance['title'] : '';
$text = isset( $instance['text'] ) ? $instance['text'] : '';
?>
<p>
<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
<?php esc_html_e( 'Title:', 'textdomain' ); ?>
</label>
<input
class="widefat"
id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
type="text"
value="<?php echo esc_attr( $title ); ?>"
>
</p>
<p>
<label for="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>">
<?php esc_html_e( 'Text:', 'textdomain' ); ?>
</label>
<textarea
class="widefat"
id="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>"
name="<?php echo esc_attr( $this->get_field_name( 'text' ) ); ?>"
rows="5"
><?php echo esc_textarea( $text ); ?></textarea>
</p>
<?php
}
public function update( $new_instance, $old_instance ) {
$instance = array();
$instance['title'] = isset( $new_instance['title'] )
? sanitize_text_field( $new_instance['title'] )
: '';
$instance['text'] = isset( $new_instance['text'] )
? wp_kses_post( $new_instance['text'] )
: '';
return $instance;
}
}
Регистрация виджета:
add_action( 'widgets_init', 'register_foo_widget' );
function register_foo_widget() {
register_widget( 'Foo_Widget' );
}
Пример с пространством имен
Если класс виджета находится в пространстве имен, при наследовании нужно указывать глобальный класс \WP_Widget.
namespace My_Plugin\Widgets;
class Foo_Widget extends \WP_Widget {
public function __construct() {
parent::__construct(
'foo_widget',
__( 'Foo Widget', 'textdomain' )
);
}
public function widget( $args, $instance ) {}
}
Регистрация:
add_action( 'widgets_init', __NAMESPACE__ . '\\register_widgets' );
function register_widgets() {
register_widget( __NAMESPACE__ . '\\Foo_Widget' );
}
Или через полное имя класса:
add_action( 'widgets_init', 'my_plugin_register_widgets' );
function my_plugin_register_widgets() {
register_widget( 'My_Plugin\\Widgets\\Foo_Widget' );
}
Регистрация областей виджетов
Область виджетов регистрируется функцией register_sidebar() на хуке widgets_init.
add_action( 'widgets_init', 'theme_register_widget_areas' );
function theme_register_widget_areas() {
register_sidebar(
array(
'name' => __( 'Sidebar', 'textdomain' ),
'id' => 'sidebar-1',
'description' => __( 'Main sidebar area.', 'textdomain' ),
'before_widget' => '<section id="%1$s" class="widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title">',
'after_title' => '</h2>',
)
);
}
Параметры register_sidebar():
name- название области в админке.id- уникальный ID области. Лучше всегда задавать вручную.description- описание области в админке.class- дополнительный CSS-класс в интерфейсе админки.before_widget- HTML перед каждым виджетом.after_widget- HTML после каждого виджета.before_title- HTML перед заголовком виджета.after_title- HTML после заголовка виджета.before_sidebar- HTML перед всей областью виджетов.after_sidebar- HTML после всей области виджетов.show_in_rest- показывает область в REST API.
В before_widget можно использовать плейсхолдеры:
%1$s- ID конкретного экземпляра виджета;%2$s- CSS-классы виджета.
Пример:
'before_widget' => '<section id="%1$s" class="widget %2$s">',
WordPress может сгенерировать id автоматически, но так лучше не делать. Автоматический ID зависит от порядка регистрации областей и может измениться при смене темы или плагинов.
Регистрация нескольких областей
Для нескольких областей лучше вызвать register_sidebar() несколько раз, чтобы у каждой области были понятные id, name и описание.
add_action( 'widgets_init', 'theme_register_widget_areas' );
function theme_register_widget_areas() {
$sidebars = array(
'footer-1' => __( 'Footer 1', 'textdomain' ),
'footer-2' => __( 'Footer 2', 'textdomain' ),
'footer-3' => __( 'Footer 3', 'textdomain' ),
);
foreach ( $sidebars as $id => $name ) {
register_sidebar(
array(
'name' => $name,
'id' => $id,
'before_widget' => '<section id="%1$s" class="widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title">',
'after_title' => '</h2>',
)
);
}
}
Функция register_sidebars() тоже существует, но в большинстве случаев удобнее использовать несколько вызовов register_sidebar(), потому что так проще задать уникальные параметры для каждой области.
Отображение областей виджетов
Область виджетов выводится функцией dynamic_sidebar().
<?php dynamic_sidebar( 'sidebar-1' ); ?>
Функция принимает ID, имя или номер области виджетов и возвращает true, если область найдена и была выведена.
Обычно перед выводом проверяют, есть ли в области активные виджеты:
<?php if ( is_active_sidebar( 'sidebar-1' ) ) : ?> <aside class="site-sidebar" role="complementary"> <?php dynamic_sidebar( 'sidebar-1' ); ?> </aside> <?php endif; ?>
Так пустая HTML-обертка не будет выводиться, если пользователь не добавил виджеты.
Fallback для пустой области
Если нужно вывести запасной HTML, когда область пустая, используйте else.
<aside class="site-sidebar" role="complementary"> <?php if ( is_active_sidebar( 'sidebar-1' ) ) : ?> <?php dynamic_sidebar( 'sidebar-1' ); ?> <?php else : ?> <p><?php esc_html_e( 'Add widgets to this sidebar in the admin area.', 'textdomain' ); ?></p> <?php endif; ?> </aside>
Отображение одного виджета напрямую
Отдельный виджет можно вывести без области виджетов через функцию the_widget().
the_widget( 'WP_Widget_Search' );
С настройками экземпляра:
the_widget( 'WP_Widget_Recent_Posts', array( 'title' => __( 'Recent Posts', 'textdomain' ), 'number' => 5, ), array( 'before_widget' => '<section class="widget widget_recent_entries">', 'after_widget' => '</section>', 'before_title' => '<h2 class="widget-title">', 'after_title' => '</h2>', ) );
the_widget() полезна, когда виджет нужно вывести программно в конкретном месте шаблона, без управления через область виджетов.
Удаление виджетов и областей
Зарегистрированный виджет можно убрать функцией unregister_widget().
add_action( 'widgets_init', 'remove_default_widgets', 11 );
function remove_default_widgets() {
unregister_widget( 'WP_Widget_Meta' );
unregister_widget( 'WP_Widget_Calendar' );
}
Область виджетов можно убрать функцией unregister_sidebar().
add_action( 'widgets_init', 'remove_theme_sidebars', 11 );
function remove_theme_sidebars() {
unregister_sidebar( 'sidebar-1' );
}
При удалении области сами настройки виджетов из базы данных не обязательно удаляются сразу. Они просто перестают выводиться, потому что область больше не зарегистрирована.
Где хранятся настройки
Настройки виджетов хранятся в таблице wp_options.
Основные опции:
sidebars_widgets- список областей виджетов и ID виджетов, которые к ним привязаны.widget_{$id_base}- настройки всех экземпляров виджета с указанным$id_base.
Например, для виджета с $id_base = 'foo_widget' настройки будут храниться в опции:
widget_foo_widget
Каждый экземпляр виджета имеет свой номер. Поэтому один и тот же виджет можно использовать несколько раз с разными настройками.
Стандартные виджеты WordPress
Некоторые классы стандартных виджетов:
WP_Widget_Pages- страницы.WP_Widget_Calendar- календарь.WP_Widget_Archives- архивы.WP_Widget_Media_Audio- аудио.WP_Widget_Media_Image- изображение.WP_Widget_Media_Gallery- галерея.WP_Widget_Media_Video- видео.WP_Widget_Meta- мета-ссылки.WP_Widget_Search- поиск.WP_Widget_Text- текст.WP_Widget_Categories- рубрики.WP_Widget_Recent_Posts- последние записи.WP_Widget_Recent_Comments- последние комментарии.WP_Widget_RSS- RSS.WP_Widget_Tag_Cloud- облако тегов.WP_Nav_Menu_Widget- меню навигации.
Их можно вывести через dynamic_sidebar(), добавить в область виджетов через админку или вызвать напрямую через the_widget().
Хуки Widgets API
widgets_init
Основной хук для регистрации виджетов и областей виджетов.
add_action( 'widgets_init', 'theme_widgets_init' );
widget_title
Фильтрует заголовок виджета перед выводом.
$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
Пример:
add_filter( 'widget_title', 'theme_change_widget_title' );
function theme_change_widget_title( $title ) {
return trim( $title );
}
register_sidebar
Срабатывает после регистрации области виджетов.
add_action( 'register_sidebar', 'theme_after_sidebar_registered' );
function theme_after_sidebar_registered( $sidebar ) {
// $sidebar содержит итоговые параметры области.
}
dynamic_sidebar_before
Срабатывает перед выводом области виджетов.
add_action( 'dynamic_sidebar_before', 'theme_before_dynamic_sidebar' );
function theme_before_dynamic_sidebar( $index ) {
// $index - ID, имя или номер области.
}
dynamic_sidebar_after
Срабатывает после вывода области виджетов.
add_action( 'dynamic_sidebar_after', 'theme_after_dynamic_sidebar' );
function theme_after_dynamic_sidebar( $index ) {
// $index - ID, имя или номер области.
}
dynamic_sidebar_params
Фильтрует параметры виджета перед выводом.
add_filter( 'dynamic_sidebar_params', 'theme_filter_widget_params' );
function theme_filter_widget_params( $params ) {
$params[0]['before_widget'] = str_replace( 'widget ', 'widget custom-widget ', $params[0]['before_widget'] );
return $params;
}
Виджеты и редактор блоков
С WordPress 5.8 экран виджетов стал блочным. Это не отменяет классические PHP-виджеты на основе WP_Widget.
Классические виджеты могут отображаться в блочном редакторе через блок Legacy Widget. Обычно старые виджеты продолжают работать без переписывания на блоки.
Если форма виджета использует JavaScript в админке, код нужно инициализировать после события widget-added.
jQuery( document ).on( 'widget-added', function( event, widget ) {
// Init widget admin JS here.
} );
Также часто нужно учитывать событие widget-updated, если форма виджета перерисовывается после сохранения.
jQuery( document ).on( 'widget-updated', function( event, widget ) {
// Re-init widget admin JS here.
} );
Безопасность
Виджет должен очищать данные при сохранении и экранировать данные при выводе.
При сохранении в update() используйте подходящие функции:
sanitize_text_field()- для обычного текста.sanitize_key()- для ключей.absint()- для положительных чисел.esc_url_raw()- для URL перед сохранением.wp_kses_post()- для HTML, разрешенного как в постах.
При выводе в widget() используйте:
esc_html()- для обычного текста.esc_attr()- для HTML-атрибутов.esc_url()- для URL.wp_kses_post()- для разрешенного HTML.
Пример:
public function update( $new_instance, $old_instance ) {
$instance = array();
$instance['title'] = isset( $new_instance['title'] )
? sanitize_text_field( $new_instance['title'] )
: '';
$instance['url'] = isset( $new_instance['url'] )
? esc_url_raw( $new_instance['url'] )
: '';
return $instance;
}
echo '<a href="' . esc_url( $instance['url'] ) . '">' . esc_html( $instance['title'] ) . '</a>';
Практические рекомендации
- Регистрируйте виджеты и области виджетов на хуке widgets_init.
- Всегда задавайте стабильный
idдляregister_sidebar(). - Используйте
get_field_id()иget_field_name()для полей формы. - Очищайте данные в
update(). - Экранируйте данные в
widget(). - Не выводите пустые обертки, если область виджетов неактивна.
- Не используйте PHP4-конструктор вида
function My_Widget(). Используйте__construct(). - Для новых сложных интерфейсов лучше рассмотреть блок, а не классический виджет.
- Для простых совместимых решений
WP_Widgetвсе еще подходит.
Краткий список функций
- register_widget() - регистрирует класс виджета.
- unregister_widget() - отменяет регистрацию виджета.
- register_sidebar() - регистрирует область виджетов.
- register_sidebars() - регистрирует несколько областей виджетов.
- unregister_sidebar() - отменяет регистрацию области виджетов.
- dynamic_sidebar() - выводит область виджетов.
- is_active_sidebar() - проверяет, есть ли активные виджеты в области.
- the_widget() - выводит один виджет напрямую.
- wp_get_sidebars_widgets() - получает список областей и привязанных виджетов.