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 butões, tabelas, frames, entre outro CSS nativo, incluindo as API de configurações, nonces entre outras ferramentas que o WordPress já nos oferece. Reveja a parte I, a parte II, a parte III e a parte IV desta série.

Depois de 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 hoje trabalhar com um elemento muito especial e que nos permite manter a interoperabilidade com o sistema. O elemento “tabela” (de listagem de objectos, 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 necesá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.

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 objectos. 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 objectos externa, um feed RSS. Podíamos listar o que quiséssemos, desde ir buscar elementos a uma base de dados, mostrar os últimos emails de uma caixa de email, etc. Optei por usar um feed RSS por ser prático e mais fácil de fazer concretizar esta tarefa.

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 localiza-se em wp-admin/includes/class-wp-list-table.php na versão 3.4 do WordPress. Se abrir esse ficheiro irá ver a classe com várias propriedades e métodos. Alguns métodos exisbem apena suma 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 ficheiro de plugin, o que temos usado em todos os outros artigos desta série. No final desse ficheiro vamos escrever a nossa classe. As boas práticas do WordPress ditam que devemos definir esta classe num ficheiro PHP separado, no entanto para manter a simplicidade e a portabilidade deste tutorial vamos manter tudo dentro do mesmo ficheiro:

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

}

Primeiro que tudo começamos por incluir o ficheiro da classe WP_List_Table por forma a podermos 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 de construir os primeiros métodos. Vamos incluir os métodos principais como assinatura na nossa classe, por forma a você ficar com 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 casa 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 é respeitante à 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 objectos que são listados na tabela assim como se a nossa tabela permite o uso de Ajax ou não.

Método ajax_user_can()

Apenas retorna se o usuário actual tem ou não permissões 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 objecto 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/objecto da tabela. Neste caso o código é simples e directo, 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 secçã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 emails, gestor de contactos, etc. O processo é sempre o mesmo porém o objectivo não tem limites.

MONTAR UMA CLASSE PARA LISTAR UM FEED

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

Primeiro que tudo 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 objecto 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 em cima, cada item é um objecto do array $items e contém 5 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 recta 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 me vou alongar aqui nesse assunto pois já foi analisado na secção anterior, no entanto se surgir alguma dúvida bastará ver o ficheiro provido no final deste artigo.

MOSTRAR A TABELA NUMA PÁGINA

Agora que temos a tabela completa é altura de mostrá-la na página que temos vindo a construir nesta série. Para isso vamos remover as opções e os elementos que colocámos 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 class="wrap">
    <?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 vais 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 em cima 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.

EXTENDENDO A CLASSE: ACÇÕES DE LINHA E 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 conseguidas 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 acçõ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 acçõ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á ser sorteáveis:

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

}

O PAINEL DE OPÇÕES PARA LISTAGEM POR PÁGINA

Neste 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 o fazer é 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á usámos 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 secçã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é para a semana para um próximo artigo.