Prover campos personalizados para o Views

Hoje vou mostrar como criar um pequeno módulo com um campo que é gravado em uma tabela própria, e em seguida disponibilizar esse campo para filtragem e ordenação no módulo Views.

Antes de começarmos, prcisamos nos ambientar. Para quem nunca criou um módulo para o Drupal, recomendo, antes de ler esse tutorial, ler essa documentação: http://drupal.org/node/508
Um outro requisito para entender plenamente esse tutorial(sua segunda parte, mais especificamente), é um pouco de conhecimento do módulo Views. Para baixar e testar esse módulo, acesse http://drupal.org/project/views Uma documentação inicial está disponível em http://drupal.org/node/47412
Ao final desse artigo, você poderá baixar o módulo de teste que vamos criar.

Sabendo essas coisas vamos lá.

Parte 1 - Criação do módulo

Para esse tutorial vamos assumir alguns padrões:

Antes de mais nada precisamos criar um módulo e, para isso, precisamos criar uma pasta com o nome do nosso módulo.
Assim, acesse a pasta modules da sua instalação do Drupal e crie uma pasta chamada modteste. Dentro dessa pasta crie, a princípio, 3 arquivos: modteste.info modteste.install modteste.module

Cada arquivo desse tem um propósito:

Abaixo temos um exemplo do arquivo modteste.info (clique na imagem para ampliar)

Explicando:

name = modteste: Esse é o nome do módulo

description = Permite criar um tipo de teste: Essa é a descrição do módulo. Ela será mostrada na área de instalação de módulos do Drupal

package = Testes: Esse é o pacote do módulo. Na área de instalação de módulos do Drupal, esse será o nome do separador (fieldset) que conterá o seu módulo

version = VERSION: Aqui é a versão do seu módulo. Se você deixar VERSION ele pegará a versão atual do Drupal. É recomendável que você defina números de versões coerentes com o Drupal que você está utilizando. Assim, se a série do Drupal para a qual o seu módulo foi feito é a 5, utilize algo como 5.x-0.1, onde: 5.x é a série para a qual o seu módulo foi feito e 0.1 é efetivamente a versão do seu módulo. Uma coisa importante sobre as versões é notar que mesmo que você faça um módulo quando o Drupal estava na versão 5.3, utilize 5.x pois você está fazendo o módulo para essa série, e não para a versão específica, uma vez que o que vem depois do ponto é só o número de bugfixes da série.

A seguir o arquivo modteste.install (clique na imagem para ampliar):

Explicando:

Esse arquivo conta, essencialmente com duas funções:

modteste_install(): função que realiza o procedimento de instalação. É executada quando se marca o checkbox e envia o formulário na área de instalação de módulos do Drupal.

modteste_uninstall(): função que realiza o procedimento de desinstalação. É executada quando o módulo é desabilitado na área de instalação de módulos do Drupal, e em seguida marcado o checkbox e enviado o formulário na aba Desinstalar, também na área de instalação de módulos do Drupal.

Note que, no exemplo, só foram feitas a criação e exclusão de tabelas. No entanto pode-se realizar qualquer ação nesse momento. Isso vai depender do que o seu módulo precisa para cada momento. Esse arquivo só é necessário, efetivamente, se você vai realizar alguma configuração no momento da instalação/desinstalação.

Por fim temos o arquivo modteste.module. Vamos ver os pontos chave desse arquivo. Pretendo escrever um outro tutorial explicando, mais detalhadamente, como criar um módulo.

Primeiro a função modteste_uninstall() que montra o formulário em si:

/**
* Implementação do hook_form().
*/

function modteste_form(&$node) {
    $type = node_get_types('type', $node);
    $form['title'] = array(
        '#type' => 'textfield',
        '#title' => check_plain($type->title_label) ,
        '#required' => TRUE,
        '#default_value' => $node->title,
        '#weight' => -5
    );
    $form['meucampo'] = array(
        '#type'=>'radios',
        '#title'=> t('Um campo a mais'),
        '#default_value' => $node->meucampo,
        '#options'=> _modteste_options() );
    $form['body_filter']['body'] = array(
        '#type' => 'textarea',
        '#title' => check_plain($type->body_label) ,
        '#default_value' => $node->body,
        '#rows' => 20,
        '#required' => TRUE);
    $form['body_filter']['filter'] = filter_form($node->format);
    return $form;
}

Essa função implementa 3 campos. Dois desses campos são padrão para qualquer tipo de conteúdo no Drupal(node): o título e o corpo(title e body).

Todo tipo no Drupal possui um título e um copro. Esses dados são, por padrão, gravados na tabela node(somente o title) e node_revisions(title e body).

O terceiro campo do formulário é o nosso campo personalizado. Esse campo será, na verdade, um conjunto de radio buttons onde podemos escolher um valor.

Um princípio importante na hora de trabalhar com o Drupal é não mexer nos módulos e tabelas que ele cria, pois essas mudanças podem se perder quando você atualizar o Drupal na próxima vez. Só mexa no que o Drupal já traz se você estiver corrigindo um bug(correção que você deve enviar para os desenvolvedores do Drupal, para poder melhorá-lo para a sua próxima versão), caso contrário evite.

Tendo isso em mente, criamos uma tabela com o mesmo nome do nosso módulo (modteste). Essa tabela terá dois campos: um será o nosso campo efetivamente, e o segundo o nid(node id) que é o "elo"(foreign key) entre o nosso campo e o node dele.

Se você prestou atenção no arquivo modteste.install verá que ele cria uma segunda tabela, e já a popula. Essa é uma tabela auxiliar para o nosso módulo e será nela que iremos guardar os valores possíveis para o nosso campo.
Como já foi dito esse terceiro campo(meucampo) é um conjunto de radio buttons. Para tanto, a sua definição pede uma opção chamada "#options" que nada mais é que um array contendo o par chave/valor para cada um dos radio buttons.

No nosso caso, esses valores vêm de uma tabela auxiliar chamada modteste_options então fiz uma pequena função para carregar esses valores:

function _modteste_options(){
    $result = db_query("SELECT * FROM {modteste_options}");
    while ($option = db_fetch_array($result)) {
        $options[$option['mid']] = $option['name'];
    }
    return $options;
}

Note que essa função começa com o caractere _ .Essa é uma convenção do Drupal que significa que é uma função interna para esse módulo.

Por fim temos as seguintes funções:

function modteste_insert($node) {
    if (user_access('criar um teste')) {
        db_query("INSERT INTO {modteste} (nid, meucampo) VALUES (%d, %d)", $node->nid, $node->meucampo);
    }
}
   
function modteste_update($node) {
    if (user_access('alterar seu próprio teste')) {
        db_query("UPDATE {modteste} SET nid=%d, meucampo=%d WHERE nid=%d", $node->nid, $node->meucampo, $node->nid);
    }
}

function modteste_delete($node) {
    if (user_access('alterar seu próprio teste')) {
        db_query("DELETE FROM {modteste} WHERE nid=%d", $node->nid);
    }
}

Essas três funções fazem, respectivamente, a inserção, alteração e remoção do nosso campo na tabela auxiliar.
Note que não precisamos escrever nenhuma instrução SQL para a tabela node ou node_revisions. Isso é feito internamente pelo Drupal.

Esses são os pontos chave para o nosso módulo. Como dito antes, você pode baixar o módulo no final desse artigo e ver como é feito o restante do módulo.

Parte 2 - Integração com o módulo Views

Agora vamos ver como integrar nosso módulo com o módulo Views.

Nesse artigo não vamos cobrir o módulo Views, apenas a itegração do nosso módulo com ele.

O módulo Views permite criar visualizações personalizadas para determinadas situações. Existem diversas opções para criar Views, recomendo você dar uma olhada com mais calma nesse módulo. Em breve devo lançar um screencast só com o módulo views.

O módulo Views, assim como o Drupal inteiro, trabalha com hooks que nada mais são do que funções que podem ser extendidas.

Assim, o módulo Views implementa algumas funções que podemos extender para habilitar o nosso campo personalizado para construção de views. Vamos ver 2 dessas funções:
hook_views_tables(): Nessa função são definidas tabelas usadas para o relacionamento entr o módulo Views e o seu módulo
hook_views_arguments(): Nessa função são definidos os campos que podem ser usados como argumentos(via URL) para uma view.

Abaixo temos o código dessas duas funções:

function modteste_views_tables() {
    $tables['modteste'] = array(
        'name' => 'modteste',
        'provider' => 'internal',
        'join' => array(
            'left' => array(
                'table' => 'node',
                    'field' => 'nid'
                ),
            'right' => array(
                'field' => 'nid'
            )
        ),
        "filters" => array(
            'meucampo' => array(
                'name' => t('Teste: Meu campo'),
                'list' => 'views_handler_filter_modteste_meu_campo',
                'operator' => 'views_handler_operator_andor',
                'help' => t('Permite você filtrar pelo Meu Campo'),
            ),
        ),
        'sorts' => array(
            'meucampo' => array(
                'name' => t('Teste: Meu Campo'),
                'handler' => 'views_handler_sort_modteste_sort',
                'help' => t('Ordenar pelo meu campo'),
            ),
        ),
    );
    return $tables;
}
function modteste_views_arguments() {
    $arguments = array(
        'modteste_meucampo' => array(
            'name' => t("Teste: Meu campo"),
            'handler' => "views_handler_arg_modteste_meucampo",
        ),
    );
    return $arguments;
}

Na função modteste_views_tables() nós definimos qual o nome da tabela que vamos utilizar para o relacionamento e como esse relacionamento se dará(veja o array join nessa função). Também definimos os filtros(filters) e as ordenações(sorts). Para esses dois ultimos, o módulo faz uso de funções callback, que nada mais são funções que são chamadas para executar e retornar um valor(ou conjunto de valores) sem precisarmos escrevê-la no local onde a chamamos.

É nessas funções callback que está o código a ser executado quando filtrarmos ou ordenarmos um campo.
A função modteste_views_arguments() tem um funcionamento semelhante. Ela especifica o nome de um campo(no caso acima modteste_meucampo) e uma função callback que será usada para executar a filtragem. Aparentemente ela é semelhante ao filters da outra função, mas a diferença essêncial é que ela implementa o nosso campo como um argumento para filtragem pela URL. Assim, eu posso chamar minha view, passando por argumento na URL o valor(no caso) do Meu Campo e a view fará a filtragem por nodes que tenham Meu Campo com esse valor.

Por fim, para manter as coisas organizadas, criamos um arquivo chamado views_modteste.inc e colocamos todas as funções de view nesse arquivo(as duas funções que estamos extendendo e suas callback). No arquivo modteste.module criamos então um mecanismo para que o Drupal encontre esse arquivo, criando uma função hook_init() como a abaixo:

function modteste_init() {
    if (module_exists('views')) {
        include_once('./'. drupal_get_path('module', 'modteste') .'/views_modteste.inc');
    }
}

Essa função irá carregar as nossas funções de views, somente se exisiter o módulo views (e esse estiver habilitado).

Assim concluímos esse tutorial.
Espero que ele sirva para você.
Esteja à vontade para comentar.

Abraço