
🛠 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-plugindentro dewp-content/plugins/e um ficheiromy-widget-plugin.phpcom o cabeçalho do plugin, depois ative-o no painel de administração - Declare uma classe que estenda
WP_Widgete faça override dos métodos__construct(),form(),update()ewidget() - Registe o widget com a função
register_widget()no hookwidgets_initpara que o WordPress o possa ver na lista - No método
form(), renderize os campos de configuração; emupdate(), sanitize e guarde os dados viasanitize_text_field() - No método
widget(), produza o conteúdo no front-end, escapando os valores comesc_html()ewp_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 /* 3 Plugin Name: My Widget Plugin 4 Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/ 5 Description: Adds a customizable widget with text, textarea, checkbox, and dropdown. 6 Version: 1.0 7 Author: AJ Clarke 8 Author URI: https://www.wpexplorer.com/ 9 License: 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 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 /* ... 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 28 function my_register_custom_widget() { 29 register_widget( 'My_Custom_Widget' ); 30 } 31 add_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.
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( '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.
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 }
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).
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__( '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 /* 3 Plugin Name: My Widget Plugin 4 Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/ 5 Description: Adds a customizable widget with text, textarea, checkbox, and dropdown. 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( '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 144 function my_register_custom_widget() { 145 register_widget( 'My_Custom_Widget' ); 146 } 147 add_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. Owp_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 sempresanitize_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_Widgetfunciona 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 comWP_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_initfoi disparado? Adicioneerror_log( 'Widget registered' )à função de registo e verifique os logs. Segundo: há um erro fatal no construtor? Ative oWP_DEBUGno wp-config.php. Terceiro: o nome da classe emregister_widget()corresponde ao nome da classe que estendeWP_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.



