Criando um controle de seletor de link CMB2 personalizado para WordPress
Neste tutorial, veremos como você pode criar um controle personalizado para estender a funcionalidade do CMB2 (Custom Meta Boxes 2) do WebDevStudios.
Eu desenvolvo sites (e aplicações web) com o WordPress CMS (Content Management System), e quando um novo projeto chega você pode garantir que haverá um requisito para eu desenvolver ‘Custom Meta Boxes’ para permitir que o usuário tenha um bom controle sobre o conteúdo e layout dos sites.
Vou detalhar como eu construí o controle do CMB2 Link Picker para CMB2 (disponível em todos os bons repositórios de plugins do WordPress). Uma captura de tela da qual pode ser vista abaixo.
O controle CMB2 ‘Link Picker’ em ação
O Link Picker aciona a caixa de diálogo ‘Inserir/editar link’ do WordPress quando você clica no botão ‘Escolher’. Isso pode ser visto na captura de tela abaixo:
Apertar o botão permite que você escolha um link (ou adicione o seu próprio)
Tenho certeza de que você concordará que ter um controle como esse é incrivelmente útil se você quiser dar aos editores do seu site a capacidade de adicionar um link e também pesquisar no WordPress por seus links internos, em vez de recortar e colar os links em um link campo.
Introdução / História
Para quem não sabe, uma meta box fica na tela do editor de uma postagem do WordPress e provavelmente conterá vários controles de formulário (caixas de texto, listas suspensas, caixas de seleção etc…). Esses controles permitem que os usuários do seu site alterem facilmente um texto ou funcionalidade personalizada no site.

Um exemplo de uma meta box com vários controles de formulário
O WordPress permite criar meta caixas usando funções (como [add_meta_box](https://developer.wordpress.org/reference/functions/add_meta_box/)), mas criar meta caixas dessa maneira pode ser um processo demorado, com muita repetição de código (especialmente se você quiser usar os mesmos controles de formulário em vários projetos).
Por que CMB2?
Alguns de vocês podem ter ouvido falar de Campos Personalizados Avançados (ACF), que fornece uma GUI (Interface Gráfica do Usuário) que permite criar meta caixas diretamente com o WordPress.
O ACF, na minha opinião, não é uma ótima ferramenta para qualquer solução web que seja escalável. O plug-in depende muito dos dados armazenados no banco de dados. Isso causa dor ao implantar alterações em um site, pois você não pode simplesmente enviar seu código e ver as alterações instantaneamente. Em vez disso, você precisa fazer o trabalho novamente nos vários ambientes de implantação (staging, live, et al). Então precisávamos de uma solução que nos permitisse criar meta boxes programaticamente. Digite CMB2.
Antes de adotarmos o CMB2, anteriormente usávamos HM Custom Meta Boxes daqueles adoráveis humanos da Human Made (que começou como um fork do precursor do WebDevStudio para o CMB2, ‘Custom Meta Boxes’).
Adoramos HM Custom Meta Boxes e, com os mais simples trechos de código, poderíamos criar rapidamente Meta Boxes personalizadas para fazer praticamente qualquer coisa!
HM Custom Meta Boxes Markup Example (esta é a marcação para o Instagram Meta Box na primeira captura de tela)
Então, por que a mudança para o CMB2? Bem, o HM Custom Meta Boxes infelizmente não estava recebendo muito amor (falei com seu desenvolvedor líder e ele é um homem muito ocupado), enquanto o CMB2 estava avançando com novos recursos, novos controles e ganhou força na comunidade WordPress com muitas pessoas adotando-o e lançando plugins para estendê-lo (incluindo várias de nossas agências parceiras).
Finalmente, como você deve ter percebido, trabalhar com o CMB2 é tão incrivelmente simples quanto estávamos acostumados, pois ambas as plataformas compartilham um ancestral comum.
Tutorial
Antes de começarmos, todo mundo tem seu próprio conjunto de ideais sobre como criar um plugin WordPress, e eu tentei alguns, no entanto o tutorial sobre ‘ Root Composition in WordPress ‘ de Tom J Nowell, mudou completamente a maneira como eu trabalho. Acho sua abordagem limpa, simples e facilita a manutenção futura de qualquer plug-in. Se você pegar a fonte do plugin Link Picker for CMB2, poderá ver os métodos que ele ensina na prática.
Construindo o formulário
Para construir o formulário que renderiza o Link Picker, a primeira coisa que precisamos fazer é entrar na cmb2_render_[control_name]ação. Como chamei esse controle de ‘link_picker’, podemos completar o gancho assim:
<?php
add_action( 'cmb2_render_link_picker', array( $this, 'cmb2_render_link_picker' ), 10, 5 );
`
Para aqueles que não entendem muito bem o add_actiongancho, funciona da seguinte forma:
- O primeiro argumento
cmb2_render_link_pickeré o nome do gancho ao qual queremos nos conectar. - O segundo argumento
array( $this, 'cmb2_render_link_picker' )é a função que queremos chamar quando esse gancho for executado. Observe que estou envolvendo isso em uma matriz, com$thiso primeiro parâmetro, porque estou chamando a função dentro de uma classe. Se você não estiver trabalhando com classes, você pode simplesmente usar o nome da funçãocmb2_render_link_picker. - O
10, é a ordem que queremos que a função dispare (quanto menor o número, mais cedo ela dispara quando a ação é chamada). - A
5é a quantidade de parâmetros que serão passados para a função que estou chamando (isso ficará claro em breve).
Em seguida, criamos a função que renderizará o formulário:
<?php
public function cmb2_render_link_picker( $field, $value, $object_id, $object_type, $field_type_object) {
…
}
`
Deixei o ‘DocBlock’ no código acima que descreve o que cada um dos parâmetros passados para a cmb2_render_link_picker()função faz.
Observe que minha função começa com a publicdeclaração. Isso novamente é porque estou trabalhando dentro de uma classe. Se você não estiver trabalhando com classes, você pode omitir isso.
O valor deste campo é passado para a função através do $valueparâmetro. No caso deste campo, estaremos passando por um array, pois nosso controle possui três elementos separados para ele:
- O texto
- O URL
- Se o link abrir em uma nova janela (ou não)
Como o $valuenem sempre está definido (por exemplo, na primeira vez que o controle é renderizado), precisamos inicializá-lo com alguns valores padrão. Fazemos isso com o seguinte trecho de código:
<?php
$value = wp_parse_args(
$value,
array(
'text' => '',
'url' => '',
'blank' => 'false',) );
Podemos então começar a renderizar o formulário. Aqui está um exemplo do primeiro controle de entrada de texto:
<p>
<label for="<?php echo $field_type_object->_id( '_text' ); ?>'">
<?php echo esc_html( $field_type_object->_text( 'link_picker_text', 'Text') ); ?>
</label>
</p>
<?php
echo $field_type_object->input(
array(
'class' => 'cmb_text',
'name' => $field_type_object->_name( '[text]' ),
'id' => $field_type_object->_id( '_text' ),
'value' => $value['text'],) );
?>
Ufa! Isso parece um pouco confuso, não é? Vamos detalhar, linha por linha:
- A marca do parágrafo de abertura.
- A tag de rótulo de abertura para o controle, mas com o
foratributo definido automaticamente pelo$field_type_object_idparâmetro. Isso gerará automaticamente um ID para o controle quando ele for renderizado. - O texto do nosso rótulo, construído usando o texto do array de opções de controles (ou volta para a palavra ‘Texto’).
- A etiqueta de fechamento
- A tag de parágrafo de fechamento.
- Iniciar declaração PHP
- Use um controle de entrada (parte do
$field_type_objectpara criar uma entrada de formulário (o tipo padrão será texto). - Inicie a matriz de parâmetros
- Defina a classe da entrada.
- Defina o nome da entrada, novamente usando o
$field_type_objectauxiliar. - Defina o ID da entrada para o mesmo ID que foi definido na etiqueta da etiqueta.
- Obtenha o valor do
$value, como este é um array, queremos a chave ‘text’ para este controle. - Feche a matriz.
- Feche a função de entrada.
- Feche a declaração PHP.
A marcação do campo de formulário de URL é praticamente a mesma, apenas para usar tipos de entrada HTML5, podemos definir um parâmetro adicional de ‘tipo’ para ‘url’:
<?php
…
'type' => 'url',
…
Finalmente, queremos implementar uma lista suspensa. A marcação é muito familiar:
<?php
echo $field_type_object->select(
array(
'class' => 'cmb_dropdown',
'name' => $field_type_object->_name( '[blank]' ),
'id' => $field_type_object->_id( '_blank' ),
'options' => $blank_options,) );
Observe que a $field_type_objectfunção que estamos usando é selectgerar uma lista suspensa. Observe também que na linha 6 temos um novo atributo de options. Para isso, estamos passando uma sequência de ‘opções’. Isso é gerado antes deste controle assim:
<?php
$blank_options = '';
$blank_options .= '<option value="false" '. selected( $value['blank'], 'false', false) .'>Opens in same</option>';
$blank_options .= '<option value="true" '. selected( $value['blank'], 'true', false) .'>Opens in new</option>';
Então tudo o que precisamos fazer é envolvê-lo em alguns <div>‘s e temos nosso controle totalmente renderizado:
<?php
public function cmb2_render_link_picker( $field, $value, $object_id, $object_type, $field_type_object) {
$value = wp_parse_args( $value, array(
'text' => '',
'url' => '',
'blank' => 'false',) );
$blank_options = '';
$blank_options .= '<option value="false" '. selected( $value['blank'], 'false', false) .'>Opens in same</option>';
$blank_options .= '<option value="true" '. selected( $value['blank'], 'true', false) .'>Opens in new</option>';
?>
<div class="link-picker">
<div class="text">
<p>
<label for="<?php echo $field_type_object->_id( '_text' ); ?>'">
<?php echo esc_html( $field_type_object->_text( 'link_picker_text', 'Text') ); ?>
</label>
</p>
<?php
echo $field_type_object->input(
array(
'class' => 'cmb_text',
'name' => $field_type_object->_name( '[text]' ),
'id' => $field_type_object->_id( '_text' ),
'value' => $value['text'],
'desc' => '',) );
?>
</div>
<div class="url">
<p>
<label for="<?php echo $field_type_object->_id( '_url' ); ?>'">
<?php echo esc_html( $field_type_object->_text( 'link_picker_url', 'URL') ); ?>
</label>
</p>
<?php
echo $field_type_object->input(
array(
'class' => 'cmb_text_url',
'name' => $field_type_object->_name( '[url]' ),
'id' => $field_type_object->_id( '_url' ),
'value' => $value['url'],
'type' => 'url',
'desc' => '',) );
?>
</div>
<div class="blank">
<p>
<label for="<?php echo $field_type_object->_id( '_blank' ); ?>'">
<?php echo esc_html( $field_type_object->_text( 'link_picker_blank', 'Window') ); ?>
</label>
</p>
<?php
echo $field_type_object->select(
array(
'class' => 'cmb_checkbox',
'name' => $field_type_object->_name( '[blank]' ),
'id' => $field_type_object->_id( '_blank' ),
'options' => $blank_options,
'desc' => '',) );
?>
</div>
<div class="choose">
<p>
<label>Choose</label>
</p>
<button class="dashicons dashicons-admin-links js-insert-link button button-primary" title="<?php esc_html_e( 'Insert Link', 'cmb' ); ?>">
<span class="screen-reader-text"><?php esc_html_e( 'Choose Link', 'cmb' ); ?></span>
</button>
</div>
</div>
<p class="clear">
<?php echo $field_type_object->_desc();?>
</p>
<?php
}
E é isso! Fizemos nosso controle! O CMB2 lida automaticamente com todos os dados que queremos salvar, portanto, não há nada a fazer lá.
Estilos
A captura de tela do controle que estamos criando (próximo ao topo desta postagem) tem alguns estilos personalizados aplicados a ela para que seja renderizada em linha. Não vou entrar em como estilizar o formulário hoje, mas se você estiver curioso, pode baixar o plugin e visualizar o código-fonte.
Tornando o controle repetível
Para aqueles que desejam ficar um pouco mais avançados, você pode fazer o controle funcionar com as regiões repetíveis do CMB2. Para fazer isso, você precisa fazer um pouco de mapeamento de matriz. Para isso use o código abaixo:
<?php
public function cmb2_sanitize_link_picker( $check, $meta_value, $object_id, $field_args, $sanitize_object) {
if (! is_array( $meta_value) ||! $field_args['repeatable']) {
return $check;
}
foreach ($meta_value as $key => $val) {
$meta_value[ $key ] = null;
if(! empty( $val['url'])) {
$meta_value[ $key ] = array_map( 'sanitize_text_field', $val );
}
}
return $meta_value;
}
public function cmb2_types_esc_link_picker( $check, $meta_value, $field_args, $field_object) {
if (! is_array( $meta_value) ||! $field_args['repeatable']) {
return $check;
}
foreach ($meta_value as $key => $val) {
$meta_value[ $key ] = null;
if(! empty( $val['url'])) {
$meta_value[ $key ] = array_map( 'esc_attr', $val );
}
}
return $meta_value;
}
Escolhendo um link
É claro que o objetivo do seletor de links é integrar-se à própria funcionalidade de escolha de links do WordPress, fazendo com que a tela de diálogo ‘Inserir/editar link’ apareça quando o botão ‘Escolher’ for clicado.
Para que isso aconteça, dependemos muito do JavaScript. Em particular, estou usando jQuery para fazer as coisas acontecerem.
Antes de mostrar o JavaScript que inicia a caixa de diálogo, devemos primeiro enfileirar o próprio JavaScript interno do WordPress, que irá pré-carregar os modais e bibliotecas das quais nosso código depende. Isso se parece um pouco com isso:
<?php
global $post_id;
if (isset( $post_id)) {
wp_enqueue_media( array( 'post' => $post_id) );
}
$plugin_js_url = plugins_url( 'js/plugin.js', ROOT );
wp_enqueue_script( 'wholesomecode', $plugin_js_url, array( 'jquery', 'jquery-ui-core', 'jquery-ui-draggable', 'jquery-ui-droppable', 'thickbox', 'wpdialogs' ), '1.0.0', true );
Como você pode ver, muitas das bibliotecas internas do WordPress dependem do jQuery para carregar o pop-up, então faz sentido que nosso gatilho de pop-up faça o mesmo. Isso é feito através do /js/plugin.jsque é carregado na linha 10 do exemplo acima.
jQuery(document).ready(function($) {
var url = $('body');
var text = $('body');
var blank = $('body');
$('body').on('click', '.js-insert-link', function(event) {
event.preventDefault? event.preventDefault(): event.returnValue = false;
event.stopPropagation();
url = $(this).closest('.link-picker').find('input.cmb_text_url ');
text = $(this).closest('.link-picker').find('input.cmb_text ');
blank = $(this).closest('.link-picker').find('input.cmb_checkbox ');
wpActiveEditor = true;
wpLink.open();
wpLink.textarea = url;
return false;
});
$('body').on('click', '#wp-link-cancel, #wp-link-backdrop, #wp-link-close', function(event) {
wpLink.textarea = url;
wpLink.close();
event.preventDefault? event.preventDefault(): event.returnValue = false;
event.stopPropagation();
return false;
});
$('body').on('click', '#wp-link-submit', function(event) {
console.log(text)
var linkAtts = wpLink.getAttrs();
linkAtts.text = $('#wp-link-text').val();
url.val(linkAtts.href);
if( linkAtts.text != '') {
text.val(linkAtts.text);
}
if (linkAtts.target == '_blank') {
blank.prop('checked', true);
} else {
blank.prop('checked', false);
}
wpLink.textarea = url;
wpLink.close();
event.preventDefault? event.preventDefault(): event.returnValue = false;
event.stopPropagation();
return false;
});
});
Usando as classes que envolvemos nossos controles de formulário, o JavaScript direciona os controles e envia o resultado selecionado do pop-up do seletor de links para os campos de controle relevantes.
Usando o controle
Então, depois de ver o tutorial acima, e possivelmente depois de revisar o código-fonte do plugin Link Picker for CMB2, ou apenas baixar minha versão, você pode estar se perguntando como usar a coisa com o CMB2. Bem, não poderia ser mais fácil:
<?php
function wholesomecode_create_meta_boxes() {
$prefix = '_profile_';
$cmb = new_cmb2_box(
array(
'id' => 'cta',
'title' => __( 'Call to Action', 'cmb2' ),
'object_types' => array( 'profile' ),
'context' => 'normal',
'priority' => 'low',
'show_names' => true,) );
$field1 = $cmb->add_field(
array(
'name' => __( 'Link Picker', 'cmb2' ),
'id' => $prefix. 'cta_link',
'type' => 'link_picker',) );
}
add_action( 'cmb2_admin_init', 'wholesomecode_create_meta_boxes' );