Back-End

21 jan, 2013

Guia completo de integração com o ambiente gráfico do WordPress – Parte 05

Publicidade

Este artigo é a quinta parte da série sobre desenvolvimento de plugins e temas com o foco na integração com o ambiente nativo da administração do WordPress, o uso dos componentes gráficos, tais como botões, tabelas, frames, entre outro CSS nativo, incluindo as API de configurações, nonces, entre outras ferramentas que o WordPress já nos oferece. Veja a última parte publicada desse artigo.

Depois de ter abordado a forma como usar diferentes tipos de elementos UI na nossa página, e vermos na última parte como usarmos a API de opções do WordPress, vamos trabalhar hoje com um elemento muito especial e que nos permite manter a interoperabilidade com o sistema. O elemento “tabela” (de listagem de objetos, como posts) é muito especial porque ela apresenta uma interface com inúmeras funcionalidades. Esta interface é feita através de uma classe PHP que nos possibilita renderizar a tabela sem ter que escrever o HTML necessário para os elementos que a compõem. Desta forma, obtemos uma página de listagem com um uso consistente e, mais uma vez, de maneira a manter a compatibilidade com versões futuras do WordPress sem muito esforço.

Selecção_062

Para criar uma nova tabela numa página da administração, teremos que criar uma subclasse herdeira da classe WP_List_Table por forma a podermos definir todos os parâmetros necessários e alimentarmos a nossa tabela com objetos. No artigo de hoje iremos focar nos passos necessários para criar este elemento e para isso vamos alimentar a nossa tabela com uma fonte de objetos externa: um feed RSS. Podíamos listar o que quiséssemos, desde buscar elementos em uma base de dados, mostrar os últimos e-mails de uma caixa, etc. Optei por usar um feed RSS por ser prático e mais fácil de fazer.

A Classe WP_LIST_TABLE

Esta é a classe central que cria todas as tabelas das páginas de listagem na administração do WordPress. É com esta classe que iremos fazer então a nossa tabela. Ela está localizada em wp-admin/includes/class-wp-list-table.php na versão 3.4 do WordPress. Se abrir esse arquivo, verá a classe com várias propriedades e métodos. Alguns métodos exibem apenas uma mensagem de erro, pedindo para que a classe herdeira (a que vamos construir) recrie esse método com as suas especificidades. É exactamente isso que iremos fazer em primeiro lugar.

Vamos abrir o nosso arquivo do plugin, o que temos usado em todos os outros artigos desta série. No final desse arquivo vamos escrever a nossa classe. As boas práticas do WordPress ditam que devemos definir esta classe num arquivo PHP separado, no entanto, para manter a simplicidade e a portabilidade o processo, vamos manter tudo dentro do mesmo arquivo:

include_once( ABSPATH . '/wp-admin/includes/class-wp-list-table.php' );
class WP_RSS_List_Table extends WP_List_Table {

}

Primeiro, incluimos o arquivo da classe WP_List_Table de forma que possamos criar a nossa classe WP_RSS_List_Table extendida. Este é o escopo da classe, no entanto ela ainda não produz qualquer efeito. Precisamos, primeiro, construir os primeiros métodos. Vamos incluir os métodos principais como assinatura na nossa classe, assim você você tem uma ideia geral do workflow necessário:

include_once( ABSPATH . '/wp-admin/includes/class-wp-list-table.php' );
class WP_RSS_List_Table extends WP_List_Table {

// Método contrutor da classe
function __construct() {
// Vamos chamar o construtor da classe principal
parent::__construct( array(
'plural'    => 'Nome do objecto plural',
'singular'  => 'Nome singular',
'ajax'      => true, // Se permite Ajax na tabela
)
);
}

// Este método permite avaliar se o usuário atual
// tem permissões para efectuar alterações via Ajax
// Obrigatório de passarmos ajax=true no construtor
function ajax_user_can() {}

// Este método prepara os items e aplica-os na variável $items
// que deverá ser sempre um array.
// No nosso caso essa variável será um array com todos os items
// vindos do feed RSS.
// Aqui também criamos a paginação da tabela e criamos as colunas.
// Este método é óbrigatório
function prepare_items() {}

// Este método lista as colunas que temos disponíveis
function get_columns() {}

// Gere a coluna em que aparece a checkbox
// Não é obrigatório se você não necessitar de uma checkbox
function column_cb( $item ){}

// Aqui vai todo o workflow para as colunas listadas na tabela
function column_default( $item, $column_name ){}

}

Estes são os métodos principais que você deve colocar para iniciar a sua classe. Iremos agora analisar cada um dos métodos e criar o workflow necessário.

Rotinas nos métodos da classe

  • O método _construct()

Este é o construtor da classe que herda obrigatoriamente o construtor da classe pai, no entanto tudo o que diz respeito à inicialização de fontes de dados e criação de rotinas deve ser especificado aqui também. Por exemplo, incluir uma classe que vai fazer a ligação e iniciar essa conexão.

Este código:

parent::__construct( array(
'plural'    => 'Nome do objecto plural',
'singular'  => 'Nome singular',
'ajax'      => true, // Se permite Ajax na tabela
)
);

serve para passar certas informações úteis à classe construtora pai, como a nomenclatura usada para os objetos que são listados na tabela assim como a nossa tabela permite o uso de Ajax ou não.

  • Método ajax_user_can()

Apenas retorna se o usuário atual tem ou não permissão para usar as ferramentas Ajax da tabela, como fazer edições inline ou sortear os dados por coluna. O mais óbvio aqui é analisar através da função current_user_can(), colocando em parâmetro a capacidade que o usuário necessita para editar o tipo de objeto que listamos.

  • Método prepare_items()

Este é muito importante. É aqui que tudo se passa, onde os dados são recebidos e sorteados e onde definimos o cabeçalho da tabela e a paginação. Este é o método que, por natureza, faz o que é necessário.

function prepare_items() {

// Esta variável será um array com todos os objectos a serem mostrados na tabela
$this->items = array(
0 => (object) array(
'ID' => 1,
'name' => 'Nome do Objecto',
[...]
),
[...]
);

// Contamos o número total de items...
$total_items = count( $this->items );

// Fazemos o cáclculo de páginas necessárias para mostrar todos os items
$total_pages = ceil( $total_items / 20 );

// ...e colocamos tudo num array que será atribuido ao método
// da classe que guarda os elementos de paginação
$this->set_pagination_args( array(
'total_items' => $total_items,
'total_pages' => $total_pages,
'per_page' => 20
)
);

// Vamos agora configurar o cabeçalho da tabela através de um array.
// Guardamos os dados acerca do cabeçalho na variável responsável
// por manter os dados relativos ao cabeçalho.
$this->_column_headers = array(
$this->get_columns(), // Vai buscar todas as colunas definidas
array(), // Poderá listar aqui as chaves das colunas que queremos esconder
$this->get_sortable_columns() // Vai buscar as colunas que são sorteáveis
);
}
  • Método get_columns()

Neste método listamos as colunas que serão usadas na tabela. Desta forma, você só terá que montar um array com a chave (que será passado como ID) da coluna e o correspondente nome (ou label):

function get_columns() {
$posts_columns = array();
$posts_columns['cb'] = '<input type="checkbox" />';
$posts_columns['name'] = 'Nome';
[...]

return $posts_columns;

}
  • Método column_cb() 

Este método mostra a checkbox, caso pretenda, para cada linha/objeto da tabela. Neste caso o código é simples e direto, sem necessidade de adaptação:

function column_cb( $item ){
return sprintf(
'<input type="checkbox" name="%1$s[]" value="%2$s" />',
$this->_args['singular'],
$item->ID
);
}
  • Método column_default()

Este método lista a rotina de todas as outras colunas definidas na nossa classe, para cada linha:

function column_default( $item, $column_name ){
switch( $column_name ){
case 'name':
echo '<a href="' . esc_url( $item->permalink ) . '" target="_blank">';
echo $item->name;
echo '</a>';
break;
}

}

Estes são os métodos principais da classe. Se for necessário, você poderá extender ainda mais a classe, modificando os métodos que estiverem definidos. Por exemplo, você quererá ter colunas sorteáveis, nesse caso, poderá definir o método get_sortable_columns() e passar um array com as colunas que pretende que sejam sorteáveis.

Na próxima seção, iremos aplicar um exemplo prático de listagem na tabela usando os elementos retornados de um feed. Você poderá aplicar no que pretender, leitor de e-mails, gestor de contactos, etc. O processo é sempre o mesmo, porém o objetivo não tem limites.

Montar uma classe para listar um feed

Selecção_063

Na seção anterior, conhecemos o básico de como aplicar a classe WP_List_Table numa página do WordPress. Nesta seção, vamos estender estes conhecimentos e aplicá-los em algo que pode realmente ser útil. Não irei explicar todos os passos, como fiz na secção anterior, antes de colocar dentro de alguns métodos o que é necessário alterar ou acrescentar na nossa classe para atingirmos o pretendido.

Primeiro, vamos incluir a biblioteca de feeds do WordPress e receber a URL de feed pretendida no método construtor:

// Método contrutor da classe
function __construct( $url ) {
// Vamos incluir o SimplePIE incluindo no WordPress.
// Desta forma torna-se mais fácil buscar feeds e retorná-los num array.
include_once(ABSPATH . WPINC . '/feed.php');

// Guardar a URL na variável
$this->rss = esc_url( $url, array( 'http', 'https') );

// Vamos chamar o construtor da classe principal
parent::__construct( array(
'plural'    => 'Feeds',
'singular'  => 'Feed',
'ajax'      => true,
)
);

}

Agora vamos até ao método prepare_items() e vamos alimentar a variável $items com os items retornados do Feed, mas para isso é necessário seguirmos algumas regras, pois cada item guardado deve estar na forma de objeto simples. A rotina é muito simples, usando um foreach:

// Este método prepara os items e aplica-os na variável $items
// que deverá ser sempre um array.
// No nosso caso essa variável será um array com todos os items
// vindos do feed RSS.
// Aqui também criamos a paginação da tabela e criamos as colunas.
function prepare_items() {

// Vamos buscar o feed da Escola WordPress e retornamo-lo para o array de items.
$this->rss = fetch_feed( $this->rss );
if ( is_wp_error( $this->rss ) ) {
$this->items = 0;

} else {
$rss_items = $this->rss->get_items( 0, $this->rss->get_item_quantity( 999 ) );

foreach ( $rss_items as $id => $item ) {
$this->items[] = (object) array(
'ID'            => (int) $id,
'title'         => $item->get_title(),
'permalink'     => $item->get_permalink(),
'description'   => $item->get_description(),
'date'          => strtotime( $item->get_date( 'Y-m-d H:i:s' ) ),
);

}

}

Como é possível analisar no código acima, cada item é um objeto do array $items e contém cinco propriedades (ID, title, permalink, description e date) cada um com os dados relativos ao item. Agora que temos os dados organizados num array, estamos quase na reta final para a renderização dos items na tabela, falta apenas definir as colunas da tabela e construir o código para cada uma delas. Não vou me alongar aqui, no entanto, se surgir alguma dúvida bastará ver o arquivo provido no final deste artigo.

Mostrar a tabela numa página

Agora que temos a tabela completa, é hora de mostrá-la na página que estamos construindo nesta série. Para isso, vamos remover as opções e os elementos que colocamos lá e incluir o seguinte código, modificando a função:

// Esta função faz o display do conteúdo da página
// No futuro iremos colocar tudo o que é visível aqui
function test_ui_submenu_page_callback() {
$wp_list_table = new WP_RSS_List_Table( 'http://www.escolawp.com/feed/?posts_per_page=99' );
$wp_list_table->prepare_items();

?>
<div>
<?php screen_icon(); ?>
<h2>
Plugin de Teste UI
</h2>

<!-- O corpo da página vem aqui -->

<!-- Criamos um formulário a apontar para a página options.php -->
<?php $wp_list_table->views(); ?>

<form id="formulario" action="options-general.php?page=plugin-test-ui" method="post">
<?php $wp_list_table->search_box( 'Pesquisar', 'feed' ); ?>

<?php $wp_list_table->display(); ?>

</form>

</div>
<?php

}

Irei apenas explicar o que é novo nesta função, que é relativo ao setup e renderização da tabela. O código

$wp_list_table = new WP_RSS_List_Table( 'http://www.escolawp.com/feed/?posts_per_page=99' );
$wp_list_table->prepare_items();

prepara a tabela. Em primeiro lugar, criamos uma instância da classe passando o URL do feed que pretendemos mostrar na nossa tabela. A segunda linha vai chamar o preparador da tabela, que vai recolher os items e configurar os cabeçalhos da tabela.

<?php $wp_list_table->views(); ?>

<form id="formulario" action="options-general.php?page=plugin-test-ui" method="post">
<?php $wp_list_table->search_box( 'Pesquisar', 'feed' ); ?>

<?php $wp_list_table->display(); ?>

</form>

O código acima também é bastante simples. São três métodos que renderizam três coisas diferentes. O primeiro vai renderizar os links de estado (que iremos ver à frente como criar), o segundo renderiza a caixa da pesquisa (recebendo dois parâmetros, o primeiro é o label e o segundo um ID) e o terceiro renderiza a tabela propriamente dita.

Estendendo a classe: ações de linha em massa

Existem mais algumas opções que podemos usar em conjunto com a nossa tabela.

Criar “links de posição” por cima da tabela:

Muito usado na página de listagem de posts, os links superiores de filtragem de posts por estado podem ser facilmente conseguidos acrescentando o seguinte código:

// Mostra as views de uma tabela
function get_views() {
return array(
'all' => sprintf( '<a href="%1$s">%2$s</a>', 'index.php', 'Ver Tudo' ),
'change' => sprintf( '<a href="%1$s">%2$s</a>', 'index.php', 'Mudar de Feed' ),
'um-texto' => 'Isto é apenas um texto!'
);

}

Este código é um método que retorna  um array com um conjunto de links que será renderizado antes da tabela.

Dropbox de ações em massa

// Mostra a dropdown das acções em massa
function get_bulk_actions() {
return array(
'edit' => 'Editar',
'remove' => 'Remover'
);

}

Com este código adicionado à classe, você poderá acrescentar uma dropbox de ações em massa na tabela.

Colunas sorteáveis

As colunas sorteáveis por Ajax fazem com que a ordenação dos dados por coluna seja mais fácil. Assim, bastará acrescentar o método e retornar um array com as chaves das colunas que deverão ser sorteáveis:

// Regista quais as colunas sorteáveis
function get_sortable_columns() {
return array(
'title' => 'title',
'date' => 'date'
);

}

O painel de pções para listagem por página 

Selecção_064Neste momento, já temos toda a nossa tabela de listagem montada; é necessário apenas adicionar uma opção para que o usuário possa limitar o número de items que pretender visualizar por página. A melhor maneira de fazê-lo é adicionando essa opção à tab de opções da página. Nesse sentido, iremos apenas precisar de incluir o seguinte código:

add_filter( 'set-screen-option', 'test_ui_set_screen_option', 10, 3 );

function test_ui_set_screen_option( $status, $option, $value ) {
if ( 'feeds_per_page' == $option )
return (int) $value;

}

function test_ui_general_options_input() {
global $test_ui_page;

$screen = get_current_screen();
// get out of here if we are not on our settings page
if( ! is_object( $screen ) || $screen->id != $test_ui_page )
return;

$args = array(
'label' => 'Feeds por página',
'default' => 10,
'option' => 'feeds_per_page'
);
add_screen_option( 'per_page', $args );

}

No código acima você terá que passar na função que já usamos nos artigos anteriores, que é chamada durante o carregamento da nossa página, a função add_screen_option() que entre as várias opções aceita dois argumentos: 0 nome da opção e um array com os parâmetros. Desta maneira, o usuário poderá ir às opções de tela e modificar o valor que necessita.

Por último, temos que fazer com que a classe reconheça que tal opção foi alterada. Para isso, vamos simplesmente acrescentar uma chamada a um método da classe que analisa essa opção, isto dentro da classe prepare_items():

// Vamos buscar o número de items a serem exibidos.
// O valor padrão é 20, no entanto o usuário poderá escolher quanto quer que apareça
// através da tab de opções de ecrã.
$per_page = $this->get_items_per_page( 'feeds_per_page' );

Esta variável $per_page irá ser usada no lugar de um valor (no caso da seção inicial deste artigo usamos 20 como valor para o número de artigos por página).

Download do código

Este foi o último artigo desta série. Espero que tenha sido útil para muitos que desenvolvem plugins e temas para WordPress e que, com esta série, possa ter contribuído para o crescimento integral da “harmonização” visual e o uso de elementos nativos da interface de administração do WordPress.

Até o próximo artigo!