12k
All articles

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.

OpenReplay Team
OpenReplay Team
Escrevendo um Plugin WordPress do Zero

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/plugins com 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() ou add_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á:

CampoO que o WordPress faz com eleObrigatório
Plugin NameExibe na lista de PluginsSim
DescriptionMostra abaixo do nomeNão
VersionExibe; orienta comparações de atualizaçãoNão
Requires at least / Requires PHPBloqueia a ativação em ambientes mais antigosNão
Author, License, Text DomainAtribuição, licenciamento, slug de traduçãoNã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:

  1. O plugin está realmente ativado, e não apenas presente?
  2. Cada string passada para add_action() ou add_filter() corresponde exatamente a um nome de função definido?
  3. Existe espaço em branco ou saída antes de <?php? O WordPress reporta “unexpected output” durante a ativação quando isso ocorre.
  4. Ative o WP_DEBUG no wp-config.php, com WP_DEBUG_LOG gravando erros em wp-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.

DevTools for the frontend

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

We use cookies to improve your experience. By using our site, you accept cookies.