Escrevendo um Plugin WordPress do Zero
Crie um plugin WordPress do zero com hooks, ativação e desativação, página de configurações, sanitização, escaping e salvamento seguro.
Um plugin WordPress é, em sua forma mais simples, uma pasta dentro de wp-content/plugins contendo um arquivo PHP que começa com um comentário de cabeçalho nomeando o plugin. Salve esse arquivo e o WordPress listará o plugin na tela de Plugins.
Se você vem colando trechos de código no functions.php de um tema, já conhece o problema: troque de tema e o código vai junto. Mover esse código para um plugin significa que a funcionalidade sobrevive a uma troca de tema, e essa é a principal razão pela qual o comportamento do site pertence aos plugins e a apresentação pertence aos temas.
Este artigo constrói um pequeno plugin do início ao fim: um aviso de site exibido no rodapé, editável a partir de uma página de configurações que salva com segurança. Cada bloco de código é um estado válido do plugin, então você pode parar após qualquer seção e ativar o que tiver.
Principais Pontos
- Um plugin é uma pasta em
wp-content/pluginscom um arquivo PHP cujo comentário de cabeçalho nomeia o plugin;Plugin Nameé o único campo obrigatório do cabeçalho. - O código do plugin não faz nada até que você conecte uma função a um hook com
add_action()ouadd_filter(). - Um callback de filtro deve retornar o valor que recebe; não retornar nada faz com que o valor filtrado fique vazio.
- Uma página de configurações segura combina quatro elementos: uma verificação de capacidade, um nonce, sanitização na entrada e escape na saída.
register_activation_hook()serve para definir padrões, a desativação serve para limpeza temporária, e a remoção permanente de dados pertence à desinstalação.
Como Criar o Arquivo e o Cabeçalho de um Plugin WordPress
Crie a pasta wp-content/plugins/site-notice/ e, dentro dela, o arquivo site-notice.php. O WordPress monta a tela de Plugins lendo os arquivos PHP na pasta de plugins e selecionando aqueles que começam com um cabeçalho de plugin. Todo arquivo que carrega um cabeçalho conta como um plugin separado, então coloque o cabeçalho em apenas um arquivo.
<?php
/**
* Plugin Name: Site Notice
* Description: Shows a short notice in the site footer, editable from Settings.
* Version: 1.0.0
* Requires at least: 6.5
* Requires PHP: 7.4
* Author: OpenReplay Team
* License: GPL-2.0-or-later
* Text Domain: site-notice
*/
A página de requisitos de cabeçalho lista todos os campos reconhecidos, incluindo Update URI e Requires Plugins. Os que você mais usará:
| Campo | O que o WordPress faz com ele | Obrigatório |
|---|---|---|
| Plugin Name | Exibe na lista de Plugins | Sim |
| Description | Mostra abaixo do nome | Não |
| Version | Exibe; orienta comparações de atualização | Não |
| Requires at least / Requires PHP | Bloqueia a ativação em ambientes mais antigos | Não |
| Author, License, Text Domain | Atribuição, licenciamento, slug de tradução | Não |
Salve o arquivo e o plugin aparecerá na tela de Plugins. Ele ativa sem problemas e não faz nada.
O Que Fazem os Hooks de Ativação e Desativação?
register_activation_hook() executa seu callback uma única vez, no momento em que alguém liga o plugin, o que o torna um bom lugar para gravar seus valores iniciais de opção no banco de dados. register_deactivation_hook() serve para descartar tudo aquilo de que o plugin só precisava enquanto estava em execução, sendo um cache o exemplo mais comum. Apagar coisas em definitivo, incluindo opções e tabelas personalizadas, é tarefa da desinstalação, porque as pessoas frequentemente desligam um plugin com a intenção de ligá-lo novamente mais tarde.
Adicione abaixo do cabeçalho:
function orp_activate() {
add_option( 'orp_notice_text', 'Welcome to the site.' );
}
register_activation_hook( __FILE__, 'orp_activate' );
function orp_deactivate() {
delete_transient( 'orp_notice_cache' );
}
register_deactivation_hook( __FILE__, 'orp_deactivate' );
O primeiro argumento, __FILE__, aponta para o arquivo principal do plugin. Nosso plugin ainda não armazena nada em cache, mas o callback de desativação mostra o formato: limpe artefatos temporários e deixe a opção salva intacta. O prefixo orp_ em cada nome de função e de opção evita colisões com o core e com outros plugins.
Por Que o Código do Meu Plugin Não Executa?
O código em um arquivo de plugin não executa por conta própria. O WordPress só executa uma função depois que você a conecta a um hook e, até lá, o arquivo permanece inerte. add_action() recebe o nome de um hook e um callable, com uma prioridade opcional cujo padrão é 10.
function orp_print_notice() {
$notice = get_option( 'orp_notice_text' );
if ( $notice ) {
echo '<p class="orp-notice">' . esc_html( $notice ) . '</p>';
}
}
add_action( 'wp_footer', 'orp_print_notice' );
Quando o tema dispara wp_footer, o WordPress chama todas as funções conectadas a ele, incluindo a nossa. Carregue qualquer página do front-end e o aviso aparecerá acima da tag de fechamento do body.
Adicionando um Filtro: Alterar um Valor e Retorná-lo
Uma action permite que sua função execute em um ponto do ciclo de vida do WordPress; um filter entrega um valor à sua função e espera receber de volta o valor modificado. Um callback de filtro deve retornar um valor. Se ele não retornar nada, o PHP retorna null, o WordPress leva esse null adiante, e aquilo que você estava filtrando desaparece do site. Esqueça o return em um callback de body_class e o elemento body perde suas classes; esqueça-o em the_content e todo post será renderizado vazio.
function orp_body_class( $classes ) {
if ( get_option( 'orp_notice_text' ) ) {
$classes[] = 'orp-has-notice';
}
return $classes;
}
add_filter( 'body_class', 'orp_body_class' );
Isso recebe o array de classes do body, acrescenta uma quando há um aviso definido e retorna o array, de modo que os temas possam estilizar páginas de forma diferente enquanto o aviso estiver ativo.
Como Construir uma Página de Configurações no WordPress?
Uma página de configurações são três registros: uma página de menu no hook admin_menu, e uma configuração mais sua seção e campo no hook admin_init, como explica o capítulo sobre a Settings API. add_options_page() coloca a página em Configurações; register_setting() nomeia a opção e, o que é fundamental, seu sanitize_callback.
function orp_settings_menu() {
add_options_page( 'Site Notice', 'Site Notice', 'manage_options',
'orp-site-notice', 'orp_settings_page_html' );
}
add_action( 'admin_menu', 'orp_settings_menu' );
function orp_settings_init() {
register_setting( 'orp_settings', 'orp_notice_text', array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
) );
add_settings_section( 'orp_main', 'Notice', '__return_false', 'orp-site-notice' );
add_settings_field( 'orp_notice_text', 'Notice text',
'orp_notice_field_html', 'orp-site-notice', 'orp_main' );
}
add_action( 'admin_init', 'orp_settings_init' );
function orp_notice_field_html() {
$value = get_option( 'orp_notice_text', '' );
echo '<input type="text" name="orp_notice_text" value="'
. esc_attr( $value ) . '" class="regular-text">';
}
function orp_settings_page_html() {
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<form action="options.php" method="post">
<?php
settings_fields( 'orp_settings' );
do_settings_sections( 'orp-site-notice' );
submit_button( 'Save Notice' );
?>
</form>
</div>
<?php
}
O formulário faz post para options.php, e o core cuida do salvamento. Observe que register_setting() também aceita um argumento default; nós definimos o nosso no hook de ativação, então escolha um mecanismo e mantenha a consistência.
Como Tratar o Valor Enviado com Segurança?
Uma verificação de capacidade responde se este usuário tem permissão para salvar configurações; um nonce responde se este usuário realmente teve a intenção de enviar este formulário. Uma página de configurações deixada no site de um cliente precisa dos dois, e nonces jamais devem substituir a verificação de capacidade.
Aqui, a Settings API cobre a maior parte disso: settings_fields() imprime o nonce e o core o verifica quando o formulário retorna, e o core bloqueia o salvamento a menos que o usuário atual possua manage_options, a capacidade que wp-admin/options.php aplica por padrão às páginas de configurações. Se algum dia você escrever um handler de formulário personalizado, imprima o nonce com wp_nonce_field() e verifique-o com check_admin_referer(). Adicione a proteção explícita ao callback da página, seguindo o próprio exemplo do handbook:
function orp_settings_page_html() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// ... form as above ...
}
Verifique uma capacidade, nunca um nome de papel (role) como administrator. As duas camadas restantes já estão no lugar: sanitize na entrada para o banco de dados (sanitize_text_field como sanitize_callback) e escape na saída (esc_attr() no campo, esc_html() no rodapé). As duas operações não são intercambiáveis, e pular qualquer uma delas é como uma opção armazenada se transforma em um vetor de injeção.
Ative e Confirme Que Funciona
Ative o Site Notice na tela de Plugins, carregue o front-end e verifique o aviso no rodapé. Altere o texto em Configurações, Site Notice, e recarregue. Se nada acontecer, siga esta lista em ordem:
- O plugin está realmente ativado, e não apenas presente?
- Cada string passada para
add_action()ouadd_filter()corresponde exatamente a um nome de função definido? - Existe espaço em branco ou saída antes de
<?php? O WordPress reporta “unexpected output” durante a ativação quando isso ocorre. - Ative o
WP_DEBUGnowp-config.php, comWP_DEBUG_LOGgravando erros emwp-content/debug.log, e leia o erro real do PHP em vez de adivinhar.
Agora você tem um plugin com uma opção padrão, uma action, um filter que retorna seu valor e uma página de configurações salva por meio de um nonce, uma verificação de capacidade e um sanitizador. Essa é a base que vale a pena replicar para cada trecho de código que ainda vive no functions.php: mova-o para lá, adicione um prefixo, conecte-o a um hook e mantenha intacto o par sanitizar-na-entrada, escapar-na-saída.
Perguntas Frequentes
Qual é a diferença entre wp_verify_nonce e check_admin_referer?
check_admin_referer() verifica tanto o nonce quanto o referrer para formulários e URLs em telas administrativas, e interrompe a requisição com um 403 quando a verificação falha. wp_verify_nonce() checa apenas o nonce e retorna um resultado que você mesmo trata, o que é adequado para handlers Ajax e outros contextos personalizados. Nenhum dos dois substitui uma verificação de capacidade: nonces confirmam que o usuário teve a intenção de realizar a ação, current_user_can() confirma que o usuário tem permissão para executá-la.
O que acontece com as opções salvas de um plugin quando ele é desativado?
Nada: a desativação mantém as opções no banco de dados, de modo que as configurações permanecem intactas quando o usuário reativa o plugin. Um callback de desativação deve limpar apenas coisas de vida curta, como transients ou arquivos em cache. A limpeza permanente pertence à desinstalação, implementada com register_uninstall_hook() ou com um arquivo uninstall.php na pasta do plugin. Se você usar uninstall.php, ele deve verificar se a constante WP_UNINSTALL_PLUGIN está definida antes de apagar qualquer coisa.
Um plugin WordPress pode ter mais de um arquivo PHP?
Sim, mas apenas o arquivo principal deve conter o comentário de cabeçalho do plugin. O WordPress encontra plugins lendo os arquivos PHP no diretório de plugins em busca desse cabeçalho, e cada arquivo que o contém aparece como um plugin próprio. Carregue arquivos adicionais a partir do arquivo principal com require_once, e mantenha chamadas como register_activation_hook() apontando para o arquivo principal do plugin, já que o primeiro parâmetro delas deve referenciar esse arquivo.
Um valor padrão de opção deve ser definido em register_setting ou no hook de ativação?
Use um mecanismo, não os dois. register_setting() aceita um argumento default que é retornado quando não existe valor no banco de dados, enquanto add_option() em um hook de ativação grava uma linha real uma única vez, na ativação. A abordagem da ativação garante que o valor exista em toda requisição, incluindo requisições do front-end; o default do register_setting() só se aplica onde o código de registro tiver sido executado. Misturar os dois cria duas fontes de verdade para a mesma opção.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k