De que é feito o painel de administração do WordPress
O painel de administração do WordPress parece uma única aplicação, mas tecnicamente é um conjunto de ficheiros PHP comuns no diretório wp-admin, ligados por um ficheiro de arranque e algumas dezenas de actions. Quem conhece a ordem em que essas actions correm escreve plugins que carregam scripts apenas onde são precisos e não estragam os ecrãs de outros plugins.
Este artigo descreve o diretório wp-admin, o caminho de um pedido pelo painel, a forma como os ecrãs são identificados ($hook_suffix, WP_Screen) e aquilo que distingue o painel da hierarquia de modelos do tema. Os nomes dos ficheiros e a ordem das actions vêm do código do WordPress 7.1.3 (a versão estável atual no momento da verificação) no espelho WordPress/WordPress no GitHub.
Qual é a estrutura do diretório wp-admin no WordPress
O diretório wp-admin contém os ficheiros da área de gestão do site. No nível superior há um ficheiro por ecrã: edit.php é a lista de artigos, post.php e post-new.php tratam da edição, upload.php é a Biblioteca multimédia, options-general.php, options-writing.php e os restantes options-*.php são as subpáginas de Opções, e site-health.php é o Diagnóstico do site. O endereço na barra do navegador revela, portanto, que ficheiro está a ser executado. Ao lado ficam os subdiretórios network/ (administração da rede em multisite), user/ (painel do utilizador em multisite) e maint/ (reparação da base de dados).
Ficheiros principais do diretório wp-admin
admin.php - O ficheiro de arranque do painel. Todos os ecrãs começam com require_once __DIR__ . '/admin.php';. É aqui que:
- se define a constante
WP_ADMINe se carregawp-load.php - se incluem as funções de
wp-admin/includes/admin.php - a função
auth_redirect()envia os visitantes sem sessão iniciada para o ecrã de início de sessão - se constrói o menu e se determina o ecrã atual
index.php - O Painel, a página inicial da administração. Mostra:
- os widgets do painel
- ações rápidas
- notícias do WordPress
menu.php - A definição da estrutura do menu do painel:
- registo dos itens do menu
- as permissões exigidas para cada secção
- ícones e etiquetas do menu
Subdiretórios do wp-admin
includes/ - Contém todos os ficheiros de biblioteca:
class-wp-screen.php- a classe que gere os ecrãs do paineltemplate.php- funções auxiliares para modelosmedia.php- gestão de multimédiaajax-actions.php- handlers de AJAX
css/ - As folhas de estilo do painel:
wp-admin.css- a folha agregadora que importa as restantescommon.css- estilos comuns (o contentor.wrap, os avisos.notice)colors/- esquemas de coresforms.css- estilos de formulários
Cada folha de estilo existe em quatro variantes: a fonte, a minificada (.min.css) e duas versões para línguas escritas da direita para a esquerda (-rtl.css, -rtl.min.css). O mesmo vale para os scripts em js/: ao lado de common.js está common.min.js. Em produção o WordPress serve as versões .min, e a constante SCRIPT_DEBUG muda para os ficheiros fonte, como se explica mais abaixo.
js/ - Ficheiros JavaScript:
common.js- funcionalidades comunspost.js- o ecrã de edição de artigosmedia-upload.js- envio de multimédia
O que faz o WordPress quando se abre um endereço do wp-admin
Considere-se o pedido wp-admin/admin.php?page=minha-pagina, ou seja, um ecrã acrescentado por um plugin. Na versão 7.1.3 o ficheiro admin.php percorre estes passos, por ordem:
- Define
WP_ADMINe incluiwp-load.php, e com elewp-config.php, os plugins e o tema. A partir daquiis_admin()devolvetrue. - Inclui
wp-admin/includes/admin.php, isto é, toda a API do painel, e chamaauth_redirect(). - Inclui
wp-admin/menu.php. Este ficheiro constrói os arrays$menue$submenucom os itens do núcleo e, no fim, incluiwp-admin/includes/menu.php, que dispara a actionadmin_menu. É aí que os plugins chamamadd_menu_page(). - Dispara a action
admin_init. - A partir do parâmetro
pagecalcula o nome do hook da página (get_plugin_page_hook()) e guarda-o na variável global$hook_suffix. - Chama
set_current_screen(), que cria o objetoWP_Screenpara este ecrã. - Dispara a action
load-{$page_hook}, antes de qualquer HTML ser enviado. - Inclui
admin-header.php, exceto se o endereço tiver o parâmetronoheader. - Dispara a action com o nome
$page_hook. Foi aí queadd_menu_page()ligou antes o callback do plugin. - Inclui
admin-footer.phpe termina comexit.
Desta ordem resultam duas coisas que, na prática, estragam plugins. Primeiro, admin_menu corre antes de admin_init, e não depois, pelo que o código que regista o menu não pode partir do princípio de que algo configurado em admin_init já existe. Segundo, get_current_screen() devolve null até ao passo 6, porque a função só devolve o objeto global quando este é uma instância de WP_Screen. Chamá-la em admin_init dá null, enquanto em load-{$page_hook} já devolve um objeto funcional.
A action load-{$page_hook} é o melhor sítio para processar um formulário que redireciona depois de guardar. Os cabeçalhos HTTP ainda não foram enviados, por isso wp_safe_redirect() funciona, e o código corre só nesse ecrã. Um comentário em admin.php dá um exemplo de nome: para a página de um plugin acrescentada em Opções, o hook chama-se load-settings_page_pluginbasename.
O que é executado em admin-header.php e admin-footer.php
O cabeçalho do painel também é uma sequência de actions, e a ordem delas decide onde ligar estilos e scripts. Em admin-header.php o WordPress dispara, por ordem:
admin_enqueue_scriptscom o argumento$hook_suffix, o lugar parawp_enqueue_script()ewp_enqueue_style(),admin_print_styles-{$hook_suffix}eadmin_print_styles,admin_print_scripts-{$hook_suffix}eadmin_print_scripts,admin_head-{$hook_suffix}eadmin_head,- depois inclui
menu-header.php, que desenha o menu lateral, e disparain_admin_header, - por fim
admin_notices(ounetwork_admin_noticeseuser_admin_noticesem multisite) eall_admin_notices.
Em admin-footer.php a ordem é semelhante: in_admin_footer, admin_footer, admin_print_footer_scripts-{$hook_suffix}, admin_print_footer_scripts e admin_footer-{$hook_suffix}. As versões com sufixo correm apenas num ecrã, por isso substituem uma condição if dentro do callback. A documentação do hook admin_enqueue_scripts chama-lhe o hook adequado para incluir scripts e estilos da administração (apesar do nome, trata dos dois tipos) e recomenda usar $hook_suffix para os carregar apenas nos ecrãs que os utilizam.
Os ecrãs do núcleo, como options-general.php, fazem o mesmo sem o parâmetro page: começam com require_once __DIR__ . '/admin.php';, preparam os dados, incluem eles próprios admin-header.php, imprimem o formulário e incluem admin-footer.php.
Como funciona o sistema de modelos do painel de administração
O WordPress não usa no painel de administração um sistema de modelos tradicional como o do front-end. Em vez disso, assenta numa estrutura feita de funções callback e hooks.
Estrutura de um ecrã do painel
Cada ecrã do painel tem três partes principais. O esboço abaixo mostra a estrutura de um ecrã do núcleo, que inclui ele próprio o cabeçalho e o rodapé. O callback da página de um plugin não o faz, porque é admin.php que inclui o cabeçalho e o rodapé por ele (passos 8 e 10 acima). admin.php inclui o cabeçalho com require_once, por isso um segundo require_once no callback não faz nada, mas um require ou include simples imprime o cabeçalho do painel uma segunda vez.
// Exemplo de estrutura de um ecrã do núcleo, p. ex. wp-admin/options-general.php
require_once __DIR__ . '/admin.php';
// 1. Cabeçalho (header)
require_once ABSPATH . 'wp-admin/admin-header.php';
// 2. Conteúdo da página (content)
?>
<div class="wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<!-- Aqui fica o conteúdo propriamente dito -->
</div>
<?php
// 3. Rodapé (footer)
require_once ABSPATH . 'wp-admin/admin-footer.php';Funções callback das páginas do painel
Cada página do painel tem uma função callback associada que imprime o seu conteúdo:
// Registo de uma página do painel
add_action('admin_menu', function() {
add_menu_page(
'A minha página', // Título da página
'A minha página', // Etiqueta do menu
'manage_options', // Permissão
'minha-pagina', // Slug
'minha_pagina_callback', // Função callback
'dashicons-admin-generic',
30
);
});
// Função callback que imprime o conteúdo
function minha_pagina_callback() {
?>
<div class="wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<form method="post" action="options.php">
<?php
settings_fields('meu_grupo_opcoes');
do_settings_sections('minha-pagina');
submit_button('Guardar alterações');
?>
</form>
</div>
<?php
}Como o WordPress dá nome ao hook_suffix das páginas de plugins
O valor devolvido por add_menu_page() e add_submenu_page() é o $hook_suffix do novo ecrã. A mesma string vai para admin_enqueue_scripts, para os hooks load-* e para WP_Screen, por isso convém saber de onde vem. Quem a calcula é a função get_plugin_page_hookname() em wp-admin/includes/plugin.php, segundo o padrão {tipo}_page_{slug}:
- uma página de nível superior acrescentada com
add_menu_page()recebe o tipotoplevel, ou seja,toplevel_page_minha-pagina, - uma subpágina num menu do núcleo recebe um tipo derivado do nome do ficheiro pai, por exemplo
settings_page_minha-paginaem Opções, - uma subpágina no menu de outro plugin recebe o tipo do array
$admin_page_hooks, ondeadd_menu_page()guardasanitize_title( $menu_title ).
O último ponto é uma armadilha em sites traduzidos. Se o título do menu pai passar por __(), mudar a língua do site muda também o sufixo de todas as suas subpáginas. Um menu com o título __( 'Reports' ) dá à subpágina o sufixo reports_page_meus-relatorios, mas quando a tradução do plugin transforma o título em “Relatórios” num painel em português, sanitize_title() remove o acento e o sufixo passa a relatorios_page_meus-relatorios. O código que compara $hook com o primeiro valor escrito à mão deixa então de funcionar. É mais seguro guardar o valor devolvido por add_submenu_page() numa propriedade da classe e comparar com ele, ou verificar get_current_screen()->id.
Para que servem WP_Screen e get_current_screen
O objeto WP_Screen descreve o ecrã atual do painel. Entre as suas propriedades públicas estão id, base, parent_base, post_type, taxonomy e action. Na lista de páginas, id é edit-page, base é edit e post_type é page, por isso uma única condição sobre post_type cobre a lista, a edição e a criação de um novo elemento desse tipo.
Neste objeto estão também dois elementos de interface que muitas vezes se escrevem de raiz sem necessidade: os separadores de ajuda (add_help_tab()) e as opções de ecrã (add_option(), por exemplo o número de elementos por página numa lista). Ambos devem ser acrescentados na action load-{$page_hook}, porque só então o objeto do ecrã existe e o cabeçalho com os botões “Ajuda” e “Opções deste ecrã” ainda não foi impresso.
Que classes body recebe um ecrã do painel
admin-header.php constrói o atributo class da etiqueta body a partir de várias fontes. A primeira classe é o $hook_suffix com todos os caracteres fora de a-z0-9_- trocados por um hífen, pelo que o ecrã de um plugin recebe no body a classe toplevel_page_minha-pagina. Seguem-se, entre outras, post-type-{tipo} e taxonomy-{taxonomia} do objeto do ecrã, admin-color-{esquema} das definições do perfil do utilizador, locale-{língua}, branch-{versão} e version-{versão}, multisite e network-admin numa rede, e wp-theme-{tema} com o nome do tema ativo. Classes próprias acrescentam-se com o filtro admin_body_class. Assim, o CSS de um plugin pode visar um único ecrã com um seletor de classe, sem carregar uma folha de estilo separada.
Como funcionam os hooks no painel de administração do WordPress
Os hooks são o mecanismo que permite mudar o comportamento do painel sem mexer nos ficheiros do núcleo do WordPress.
Actions no painel de administração
As actions executam código num momento determinado:
// Acrescentar um script apenas no painel de administração
add_action('admin_enqueue_scripts', function($hook) {
// $hook contém o identificador da página atual
if ($hook === 'toplevel_page_minha-pagina') {
wp_enqueue_script(
'minha-pagina-js',
plugin_dir_url(__FILE__) . 'js/admin.js',
['jquery'],
'1.0.0',
true
);
}
});
// Alterar o cabeçalho da administração
add_action('admin_head', function() {
?>
<style>
#wpadminbar { background: #1d2327; }
</style>
<?php
});
// Acrescentar um aviso
add_action('admin_notices', function() {
?>
<div class="notice notice-success is-dismissible">
<p>As suas alterações foram guardadas.</p>
</div>
<?php
});Filtros no painel de administração
Os filtros alteram dados antes de serem mostrados ou guardados:
// Alterar o título da página do painel
add_filter('admin_title', function($admin_title, $title) {
return $title . ' | O meu site';
}, 10, 2);
// Alterar o texto do rodapé do painel
add_filter('admin_footer_text', function($text) {
return 'Feito com <a href="https://wordpress.org">WordPress</a> | Suporte técnico: A minha empresa';
});
// Alterar as colunas da lista de artigos
add_filter('manage_posts_columns', function($columns) {
$new_columns = [];
foreach ($columns as $key => $value) {
$new_columns[$key] = $value;
if ($key === 'title') {
$new_columns['minha_coluna'] = 'A minha coluna extra';
}
}
return $new_columns;
});Que classes CSS usa o painel de administração do WordPress
O WordPress traz um conjunto de classes CSS que mantém o aspeto do painel coerente.
Classes CSS dos contentores do painel
/* Contentor principal da página */
.wrap {
margin: 10px 20px 0 2px;
}
/* Título da página */
.wrap h1 {
font-size: 23px;
font-weight: 400;
margin: 0;
padding: 9px 0 4px;
line-height: 1.3;
}
/* Metaboxes */
.postbox {
min-width: 255px;
border: 1px solid #c3c4c7;
box-shadow: 0 1px 1px rgba(0,0,0,.04);
background: #fff;
}
/* Formulários */
.form-table {
border-collapse: collapse;
margin-top: 0.5em;
width: 100%;
clear: both;
}
.form-table th {
vertical-align: top;
text-align: left;
padding: 20px 10px 20px 0;
width: 200px;
line-height: 1.3;
font-weight: 600;
}Classes CSS de botões e componentes de interface
/* Botões (wp-includes/css/buttons.css) */
.wp-core-ui .button-primary {
background: var(--wp-admin-theme-color, #3858e9);
border-color: var(--wp-admin-theme-color, #3858e9);
color: #fff;
text-decoration: none;
text-shadow: none;
}
/* Avisos (wp-admin/css/common.css) */
.notice {
background: #fff;
border: none;
border-left: 4px solid #c3c4c7;
box-shadow: none;
margin: 5px 15px 2px;
padding: 8px 12px;
}
.notice-success {
border-left-color: #4ab866;
}
.notice-error {
border-left-color: #cc1818;
}Os valores acima vêm dos ficheiros buttons.css e common.css da versão 7.1.3. A cor do botão principal não está fixa no código: vem da variável CSS --wp-admin-theme-color, definida pelo esquema de cores escolhido, e #3858e9 é apenas o valor de recurso. Um plugin que fixe a cor do botão na sua própria folha de estilo vai destoar do painel de um utilizador que escolheu outro esquema no perfil. É mais seguro usar as classes do núcleo (button button-primary, notice notice-success) e não sobrepor as suas cores.
Como criar páginas próprias no painel de administração
Abaixo fica o exemplo funcional mais curto de uma página de opções construída sobre a Settings API. Um guia completo sobre arrumação de menus, papéis de clientes, esquemas de cores e widgets do painel está num artigo à parte sobre a personalização da administração do WordPress.
Como acrescentar uma página de opções simples
class Minha_Pagina_Opcoes {
public function __construct() {
add_action('admin_menu', [$this, 'acrescentar_pagina']);
add_action('admin_init', [$this, 'registar_definicoes']);
}
public function acrescentar_pagina() {
add_options_page(
'As minhas definições',
'As minhas definições',
'manage_options',
'minhas-definicoes',
[$this, 'mostrar_pagina']
);
}
public function registar_definicoes() {
register_setting('minhas_definicoes_grupo', 'minha_opcao_1');
register_setting('minhas_definicoes_grupo', 'minha_opcao_2');
add_settings_section(
'minha_seccao',
'Definições principais',
[$this, 'mostrar_seccao'],
'minhas-definicoes'
);
add_settings_field(
'minha_opcao_1',
'Primeira opção',
[$this, 'mostrar_campo_1'],
'minhas-definicoes',
'minha_seccao'
);
}
public function mostrar_pagina() {
?>
<div class="wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<form action="options.php" method="post">
<?php
settings_fields('minhas_definicoes_grupo');
do_settings_sections('minhas-definicoes');
submit_button('Guardar definições');
?>
</form>
</div>
<?php
}
public function mostrar_campo_1() {
$valor = get_option('minha_opcao_1');
?>
<input type="text" name="minha_opcao_1" value="<?php echo esc_attr($valor); ?>" class="regular-text">
<?php
}
}
new Minha_Pagina_Opcoes();Qual é a diferença entre admin-ajax.php e admin-post.php
O painel tem duas entradas separadas para pedidos que não desenham um ecrã, e ambas passam ao lado de admin.php.
admin-ajax.php trata dos pedidos AJAX. Define a constante DOING_AJAX, carrega wp-load.php e a API do painel, envia o cabeçalho X-Robots-Tag: noindex, dispara admin_init e depois a action wp_ajax_{action} para utilizadores com sessão iniciada ou wp_ajax_nopriv_{action} para visitantes. A falta do parâmetro action termina com a resposta 0 e o código 400, e a falta de um handler registado também termina com 0 e 400. Nos ecrãs do painel o endereço deste ficheiro está logo disponível na variável JavaScript ajaxurl, que admin-header.php imprime. O Plugin Handbook descreve todo o fluxo no capítulo sobre AJAX em plugins.
admin-post.php serve para formulários enviados por POST simples, a que se deve seguir um redirecionamento. Dispara admin_init e depois admin_post_{action} para utilizadores com sessão iniciada ou admin_post_nopriv_{action} para visitantes. Se nada estiver ligado a essa action, o ficheiro termina com uma resposta vazia e o código 400. O hook está descrito na página admin_post_{$action}.
O que os dois ficheiros têm em comum pesa no desempenho. admin_init corre em cada pedido a admin-ajax.php, incluindo os pedidos periódicos da Heartbeat API (a action heartbeat está na lista de actions do núcleo, e para visitantes existe wp_ajax_nopriv_heartbeat). Código pesado ligado a admin_init sem a condição wp_doing_ajax() corre, portanto, não só quando se abre um ecrã, mas a cada uma dessas batidas.
Em que difere o painel da hierarquia de modelos do tema
A palavra “modelo” no título deste artigo confunde-se facilmente com os modelos do tema. O front-end escolhe o ficheiro segundo a hierarquia de modelos, que o Theme Handbook descreve assim:
“Quando um visitante chega a qualquer página de um site WordPress, o WordPress usa a query string para decidir que modelo deve ser usado para mostrar a página. A query string contém dados do URL que ajudam a tomar essa decisão.”
Theme Handbook, Template hierarchy, tradução própria
Para temas de blocos, a mesma página da documentação indica três locais onde o WordPress procura um modelo, por ordem de prioridade: modelos criados pelo utilizador e guardados na base de dados como tipo de conteúdo wp_template, a pasta /templates do tema filho e a pasta /templates do tema pai.
O painel não tem nenhum destes mecanismos. Não há ficheiro de recurso como o index.php do tema, nem correspondência por tipo de consulta. O que aparece é decidido pelo nome do ficheiro em wp-admin ou pelo parâmetro page, e a disposição do ecrã é sempre o mesmo cabeçalho, menu e rodapé do núcleo. O tema não tem qualquer influência no aspeto do painel, a não ser o seu nome acabar na classe wp-theme-* do body. Por isso os estilos e scripts do painel incluem-se através de admin_enqueue_scripts e os do front-end através de wp_enqueue_scripts. Trocar estas duas actions acaba com a folha de estilo do painel no front-end, ou o contrário.
Como proteger o painel de administração do WordPress
Verificar as permissões do utilizador
Verifique sempre as permissões do utilizador:
// Verificar se o utilizador tem as permissões necessárias
if (!current_user_can('manage_options')) {
wp_die(__('Não tem permissão para aceder a esta página.', 'text-domain'));
}
// Verificar o nonce da ação
if (!isset($_POST['meu_nonce']) || !wp_verify_nonce($_POST['meu_nonce'], 'minha_acao')) {
wp_die(__('Token de segurança inválido.', 'text-domain'));
}Sanitizar os dados dos formulários do painel
// Sanitizar texto
$titulo = sanitize_text_field($_POST['titulo']);
// Sanitizar HTML
$content = wp_kses_post($_POST['content']);
// Sanitizar URL
$url = esc_url_raw($_POST['url']);
// Sanitizar email
$email = sanitize_email($_POST['email']);Como depurar o painel de administração do WordPress
Como ativar o WP_DEBUG no painel
// Em wp-config.php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
define('SCRIPT_DEBUG', true); // Usa scripts não minificadosA documentação Debugging in WordPress descreve SCRIPT_DEBUG como uma constante que obriga o WordPress a usar as versões de desenvolvimento dos ficheiros CSS e JavaScript do núcleo em vez das minificadas, e aponta-a como útil para testar alterações aos ficheiros .js e .css incluídos. Por omissão vale false. No painel, isto significa que o navegador recebe common.js em vez de wp-admin/js/common.min.js, e que um stack trace da consola aponta para uma linha legível em vez de uma única linha longa do ficheiro .min. A mesma página lembra que as definições têm de ficar antes do comentário que encerra a edição em wp-config.php.
Registar eventos do painel em debug.log
function log_admin_action($action, $data) {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log("Admin Action [{$action}]: " . print_r($data, true));
}
}
// Utilização
log_admin_action('user_login', ['user_id' => get_current_user_id()]);O que reter sobre o painel de administração do WordPress
Cinco pontos desta anatomia que contam quando se escreve um plugin:
- Estrutura de diretórios: saber como os ficheiros estão organizados torna a depuração e as alterações mais rápidas.
- Sistema de modelos: as funções callback e os hooks controlam a forma como um ecrã é desenhado.
- Segurança: a verificação de permissões e a sanitização dos dados são a base.
- Classes CSS: as classes do núcleo mantêm os ecrãs do plugin alinhados com o resto do painel.
- Depuração:
SCRIPT_DEBUGe o registo de depuração encurtam o caminho até à causa de um erro.
As modificações ao painel só sobrevivem às atualizações quando usam os mecanismos incorporados em vez de os contornar. Preparamos e testamos essas alterações em staging no âmbito do desenvolvimento WordPress.







