Skip to content

Tudo para WordPress, desenvolvimento web — e não só

🛠 Como criar um plugin de widget para WordPress: um guia passo a passo

🛠 Como criar um plugin de widget para WordPress: um guia passo a passo

💡 Como criar um plugin de widget no WordPress

  • Crie uma pasta my-widget-plugin dentro de wp-content/plugins/ e um ficheiro my-widget-plugin.php com o cabeçalho do plugin, depois ative-o no painel de administração
  • Declare uma classe que estenda WP_Widget e faça override dos métodos __construct(), form(), update() e widget()
  • Registe o widget com a função register_widget() no hook widgets_init para que o WordPress o possa ver na lista
  • No método form(), renderize os campos de configuração; em update(), sanitize e guarde os dados via sanitize_text_field()
  • No método widget(), produza o conteúdo no front-end, escapando os valores com esc_html() e wp_kses_post()

Passo 1: criar o esqueleto do plugin

Um widget no WordPress não é apenas uma linha no functions.php. Se quiser que as configurações persistam, que o formulário de administração funcione e que o widget sobreviva à próxima atualização do tema, precisa de o empacotar como um plugin. Isto isola o código e torna o widget independente de qualquer mudança de tema.

Comece com uma pasta vazia. Navegue até wp-content/plugins/ e crie um diretório chamado my-widget-plugin. Dentro dele, crie um ficheiro com o nome my-widget-plugin.php. Este é o ficheiro que o WordPress irá ler primeiro na ativação. Abra o ficheiro e adicione o cabeçalho padrão do plugin:

1<?php
2/*
3Plugin Name: My Widget Plugin
4Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/
5Description: Adds a customizable widget with text, textarea, checkbox, and dropdown.
6Version: 1.0
7Author: AJ Clarke
8Author URI: https://www.wpexplorer.com/
9License: GPL2
10*/

Guarde o ficheiro. Agora vá ao painel de administração do WordPress → Plugins. Se o plugin aparecer na lista, o esqueleto está pronto. Clique em «Ativar». Ainda não faz nada de útil; apenas anunciámos a sua existência. Mas o WordPress já sabe que este plugin existe e está pronto para executar o seu código. Este é um princípio importante: primeiro o registo, depois a lógica.

O funcionamento interno do plugin, que hooks são disparados na ativação e como o WordPress localiza o seu ficheiro são tudo abordado no vídeo acima. Agora passemos à parte mais interessante: a classe do widget.

Passo 2: registar o widget via WP_Widget

O WordPress fornece uma classe nativa chamada WP_Widget; faz parte do núcleo desde a versão 2.8 e continua a ser a base para quaisquer widgets personalizados. Não precisa de escrever lógica de gravação, geração de campos ou registo de raiz: basta estender a classe e fazer override de quatro métodos.

Adicione este código ao seu my-widget-plugin.php logo após o cabeçalho do plugin, antes do fecho ?>:

1// Widget class
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 /* ... admin form ... */
16 }
17
18 public function update( $new_instance, $old_instance ) {
19 /* ... saving settings ... */
20 }
21
22 public function widget( $args, $instance ) {
23 /* ... frontend output ... */
24 }
25}
26
27// Widget registration
28function my_register_custom_widget() {
29 register_widget( 'My_Custom_Widget' );
30}
31add_action( 'widgets_init', 'my_register_custom_widget' );

Vamos detalhar o que está a acontecer aqui. A classe My_Custom_Widget estende WP_Widget, o que lhe dá métodos prontos como get_field_id() e get_field_name() para gerar atributos de campos de formulário. No construtor, passamos três coisas à classe pai: um ID de widget único (caracteres latinos minúsculos, sem espaços, my_custom_widget), o seu nome legível (a função __() torna-o traduzível) e um array de opções. O parâmetro customize_selective_refresh => true permite que o widget seja atualizado no Personalizador sem recarregar a página inteira, um pequeno detalhe que poupa muita frustração durante a configuração.

A função my_register_custom_widget() chama register_widget() no hook widgets_init. É assim que o WordPress fica a saber do seu widget. Sem esta linha, não aparecerá nada no painel de administração.

Agora vamos preencher os métodos form(), update() e widget() com lógica real.

Passo 3: criar o formulário do widget no painel de administração

O formulário é o que o administrador vê ao arrastar o widget para uma barra lateral. É composto por campos: inputs de texto, dropdowns, checkboxes. Cada campo deve ser capaz de guardar o seu valor e mostrar o atual quando reaberto.

3.1. A função form() e os campos de input

Adicione este código ao método form() da sua classe. Ele cria cinco campos: um título, um input de texto, uma textarea, uma checkbox e um dropdown.

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( 'Widget Title', '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:', '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( 'Textarea:', '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( 'Show additional block', 'text_domain' ); ?>
60 </label>
61 </p>
62
63 <p>
64 <label for="<?php echo $this->get_field_id( 'select' ); ?>">
65 <?php _e( 'Display variant', '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 '' => __( '— Select —', 'text_domain' ),
73 'option_1' => __( 'Option 1', 'text_domain' ),
74 'option_2' => __( 'Option 2', 'text_domain' ),
75 'option_3' => __( 'Option 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 }

Preste atenção a duas coisas. Primeiro, substituímos a função obsoleta extract() pelo acesso direto ao array. O extract() foi há muito excluído dos padrões de codificação do WordPress; cria variáveis com nomes baseados nas chaves do array, o que é inseguro e dificulta a depuração. Segundo, cada valor de saída é passado por esc_attr(), wp_kses_post() ou esc_html(). Isto não é paranoia: os dados da base de dados podem vir de qualquer lado e a sanitização é obrigatória.

3.2. A função update() para guardar

O método update() é chamado quando se clica no botão «Guardar» no formulário do widget. A sua função é validar cada campo e devolver um array sanitizado para escrita na base de dados.

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}

Aqui usamos sanitize_text_field() em vez de wp_strip_all_tags() porque também normaliza os espaços em branco, remove caracteres de controlo invisíveis e converte a string para UTF-8 seguro. Esta é uma limpeza mais completa. O wp_strip_all_tags() deixa o texto «cru» com todos os artefactos de espaços em branco intactos. Para campos de formulário de texto, escolha sempre sanitize_text_field().

A textarea mantém wp_kses_post(): permite HTML básico (links, negrito, listas), mas remove scripts. A checkbox devolve 1 ou false, o que é legível e inequívoco na base de dados.

Passo 4: renderizar o widget no front-end

A função widget() é o que o visitante vê. Recebe dois parâmetros: $args (o invólucro do widget, tags antes e depois do título e da barra lateral) e $instance (as configurações guardadas para esta instância específica).

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__( 'Additional block activated', 'text_domain' ) . '</p>';
32 }
33
34 echo '</div>';
35
36 echo $args['after_widget'];
37}

O ponto-chave aqui: substituímos extract( $args ) pelo acesso direto via $args['before_widget']. A razão é a mesma: extract() é obsoleto e inseguro. Também envolvemos a saída em esc_html() onde se espera texto simples (o título, a string de texto, o valor do select). A textarea é renderizada através de wp_kses_post(), por isso, se o administrador inseriu um link ou texto a negrito, serão preservados.

A classe CSS wp_widget_plugin_box permite-lhe estilizar o bloco a partir da folha de estilos do tema. Sinta-se à vontade para a renomear, apenas certifique-se de que a classe é única e não entra em conflito com as classes do tema.

Código completo do plugin

Vamos juntar tudo. Aqui está o ficheiro my-widget-plugin.php completo, pronto a copiar e ativar:

1<?php
2/*
3Plugin Name: My Widget Plugin
4Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/
5Description: Adds a customizable widget with text, textarea, checkbox, and dropdown.
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( 'Widget Title', '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:', '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( 'Textarea:', '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( 'Show additional block', 'text_domain' ); ?>
68 </label>
69 </p>
70 <p>
71 <label for="<?php echo $this->get_field_id( 'select' ); ?>">
72 <?php _e( 'Display variant', '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 '' => __( '— Select —', 'text_domain' ),
79 'option_1' => __( 'Option 1', 'text_domain' ),
80 'option_2' => __( 'Option 2', 'text_domain' ),
81 'option_3' => __( 'Option 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__( 'Additional block activated', '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' );

Coloque este ficheiro em wp-content/plugins/my-widget-plugin/, ative o plugin e arraste o widget para qualquer barra lateral através de Aparência → Widgets. Preencha os campos, guarde e verifique o seu site.

O código finalizado também está disponível no GitHub: wpexplorer/my-widget-plugin, onde pode compará-lo com a versão original de 2017 e ver exatamente o que mudámos.

⁉️🤔 Perguntas frequentes

Porquê colocar um widget num plugin quando se pode simplesmente adicionar código ao functions.php do tema?

O código no functions.php está vinculado ao tema ativo. Mude de tema e o widget desaparece. Um plugin funciona independentemente do tema. Além disso, um plugin pode ser ativado seletivamente em sites diferentes, enquanto o código do tema não. Se o widget resolve uma necessidade de negócio (por exemplo, mostrar um formulário de subscrição com um layout específico), pertence a um plugin.

Porque é que o sanitize_text_field() é melhor do que o wp_strip_all_tags()?

O sanitize_text_field() não se limita a remover tags HTML; também normaliza os espaços em branco, remove caracteres de controlo invisíveis e converte a string para UTF-8. Esta é uma limpeza mais completa. O wp_strip_all_tags() deixa o texto «cru» com todos os artefactos de espaços em branco intactos. Para campos de formulário de texto, escolha sempre sanitize_text_field().

Porque substituíram o extract() pelo acesso direto ao array?

A função extract() foi excluída dos padrões de codificação do WordPress desde a versão 4.3. Cria variáveis com nomes baseados nas chaves do array no escopo local; se uma chave coincidir com uma variável existente, irá sobrescrevê-la e acabar com um bug difícil de encontrar. O acesso direto como $args['before_widget'] é legível e seguro.

Preciso de suportar widgets de bloco (Gutenberg)?

O clássico WP_Widget funciona com barras laterais baseadas em blocos através de retrocompatibilidade: o WordPress envolve-o automaticamente num Bloco de Widget Legado. Isto é suficiente para a maioria dos cenários. Se quiser criar blocos verdadeiramente nativos, consulte o Manual do Editor de Blocos, que tem uma API separada. Mas começar com WP_Widget é mais fácil: o código é mais curto, a depuração é mais rápida e funciona em todas as versões do WordPress sem plugins de compatibilidade adicionais.

Como depuro um widget que não aparece no painel de administração?

Verifique três coisas. Primeiro: o hook widgets_init foi disparado? Adicione error_log( 'Widget registered' ) à função de registo e verifique os logs. Segundo: há um erro fatal no construtor? Ative o WP_DEBUG no wp-config.php. Terceiro: o nome da classe em register_widget() corresponde ao nome da classe que estende WP_Widget? Um único erro de digitação e o widget não aparecerá.

O que se segue: do modelo ao seu próprio widget

Construímos um plugin funcional com cinco tipos de campo. Isto não é um produto final; é um modelo. Tome-o como a sua base e adapte-o às suas necessidades. Precisa de um widget de formulário de subscrição? Substitua os campos de texto por campos de email e nome, e adicione uma chamada à API do serviço de mailing dentro de widget(). Precisa de um bloco com promoções e banners? Carregue imagens através do carregador de media e renderize-as no seu markup. A mecânica é sempre a mesma: form() desenha os campos, update() guarda-os, widget() renderiza a saída.

Comece com pouco: copie o código completo acima, ative-o num site de teste e experimente as configurações. Assim que entender como os dados fluem do formulário para o front-end, comece a adicionar os seus próprios campos. E se o widget falhar com um ecrã branco, volte ao passo 2 e verifique o construtor.