Customizando a query para buscar todos os dados do usuário no WordPress

O WordPress conta com ferramentas muito boas para administrar os usuários e permite que você estenda o formulário infinitamente. Entretanto, quando falamos em resgatar estes dados fora do WordPress, para exportar para uma base excel por exemplo, fica complicado pois como as informações não ficam em colunas nas tabelas, ou você cria uma query que vai te retornar linha a linha as meta keys duplicando todos os dados da tabela users ou utiliza um código externo redundante em PHP que vai sobrecarregar o seu servidor e obrigá-lo a rever o timeout da aplicação ou ainda pior, terá que gerar um plugin só para utilizar as funções do WordPress e te retornar estes dados e que também pode vir a dar problema de timeout.

Então qual a solução mais rápida? Extrair estes dados diretamente da sua base MySQL, utilizando o concatenamento de tabelas por meio da função JOIN do SQL.

SELECT
  u1.id,
  u1.user_email,
  u1.user_pass,
  u1.user_email,

  m1.meta_value AS firstname,
  m2.meta_value AS lastname,
  m3.meta_value AS birthday
  [...]
FROM 
  wp_users u1 
JOIN 
  wp_usermeta m1 ON (m1.user_id = u1.id AND m1.meta_key = 'first_name') 
JOIN 
  wp_usermeta m2 ON (m2.user_id = u1.id AND m2.meta_key = 'last_name') 
JOIN 
  wp_usermeta m3 ON (m3.user_id = u1.id AND m3.meta_key = 'birthday')
  [...]
WHERE
  [...]

Muito mais simples, não?

Removendo a pasta public da sua aplicação Zend Framework

A primeira coisa que questionei quando iniciei os estudos do Zend Framework foi como colocar a sua aplicação dentro de uma pasta em um site já estruturado, uma vez que as pastas principais devem estar abaixo da pasta public_html do seu site. Um outro problema que encontrava era aparecer no endereço o nome da pasta public, por exemplo, http://localhost/app-de-teste/public/controller/action

A solução é bem simples.

  1. Remova o conteúdo da pasta public e jogue na pasta root. Se estiver iniciando com a sua aplicação deve haver somente um arquivo index.php
  2. Abra esse arquivo e na linha:
    defined('APPLICATION_PATH') || define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));

    altere para:

    defined('APPLICATION_PATH') || define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/application'));
  3. Crie um arquivo .htaccess na raiz do seu diretório e insira esta expressão regular:
    RewriteEngine on
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule !.(js|gif|jpg|png|css|txt)$ index.php?rt=$1 [L,QSA]

O que é feito com tudo isso? A alteração do caminho que é necessário para sua aplicação rodar e o segundo permite que se acessem arquivos (-f) e diretórios (-d) caso existam.

Criando um app em PHP para postar fotos na timeline do usuário do Facebook

A primeira coisa a fazer quando se vai trabalhar com o Facebook é buscar a versão do SDK mais recente, seja ele em PHP ou Javascript, e incluir em seu código.

require 'src/facebook.php';

Em seguida inclua o Id e o App Secret fornecidos no cadastramento da sua aplicação no Facebook. Não tratarei deste cadastro aqui pois é muito simples e com uma rápida pesquisa surgem milhares de resultados.

$appId = 'XXXXXXXX';
$appSecret = 'XXXXXXXXXX';

Cria o objeto, executa a classe e testa a conexão com o usuário:

$facebook = new Facebook(array('appId' => $appId, 'secret' => $appSecret));
$user = $facebook->getUser();
if ($user) {
  try {
    $user_profile = $facebook->api('/me');
  } catch (FacebookApiException $e) {
    error_log($e);
    $user = null;
  }
}

Se o usuário não estiver logado, dê a oportunidade:

if ($user) {
  $logoutUrl = $facebook->getLogoutUrl();
} else {
  /* Esse escopo pode ser bem menor, utilize de acordo com a sua necessidade */
  $loginUrl = $facebook->getLoginUrl(array('scope' => 'read_stream,user_website,user_videos,user_status,user_religion_politics,user_relationship_details,user_relationships,user_questions,user_photos,user_notes,user_location,user_likes,user_interests,user_groups,user_events,user_education_history,user_checkins,user_activities,email,offline_access,publish_stream,user_birthday,user_location,user_work_history,user_about_me,user_hometown'));
}

Habilite o upload da API.

$facebook->setFileUploadSupport(true);

Verifique as permissões concedidas

if ($user) {
  try{
    $permissions = $facebook->api("/me/permissions");
    if(!array_key_exists('publish_stream', $permissions['data'][0])){
      header("Location: " . $facebook->getLoginUrl(array("scope"=>"publish_stream")));
      exit;
    }
}

Aqui você criará o seu álbum. Para postar uma foto na timeline do usuário em tamanho grande, você deve criar um álbum no perfil do usuário para depois efetuar a postagem, do contrário, só é possível efetuar a postagem com a foto em miniatura.

    $album_name = 'Nome do seu álbum';
    $album_details = array('name'=>$album_name);

    /* Procura todos os álbuns do usuário para evitar que se crie um álbum repetido */
    $albuns = $facebook->api("/me/albums");
    $hasAlbum = false; // Parte do princípio que não existe o álbum.

    /* 
     * Para cada álbum encontrado, verifique se já não existe um com o mesmo nome.
     * Se já existir pega o ID do álbum
     */
    foreach($albuns['data'] as $album){
      if($album['name']==$album_name){
        $hasAlbum=true;
        $album_id = $album['id'];
      }
    }

    if(!$hasAlbum){
      $create_album = $facebook->api("/".$user."/albums", "POST", $album_details);
      $album_id = $create_album['id'];
    }

Use somente se quiser incluir uma mensagem junto com a foto, pode resgatar via $_POST

    $photo_details = array('message'=>'');

Aqui é muito importante que se use o caminho físico do arquivo no seu servidor, ou caso tenha feito um formulário para upload resgate via $_FILE.

    $file = "/home/dominio/public_html/img/foto.jpg";
    $photo_details['image'] = '@' . realpath($file);

    $upload_photo = $facebook->api('/' . $album_id . '/photos', 'post', $photo_details);

Se estiver tudo certo ao executar o script, você terá criado um álbum, postado uma foto e compartilhado na timeline do usuário.

Ajustando o xampp para atualizar wordpress no localhost

Durante algum tempo tive problemas para atualizar o WordPress quando executado no localhost, pois era pedido host, login FTP e senha para o update e até o momento não achei onde configurar estas opções no apache.

Apesar de ser fácil a atualização manual do WordPress, quando feita de forma automática, nos poupa um tempo precioso. Pensando dessa forma resolvi achar uma solução que não me tomasse muito tempo e consegui.

Procure pelo arquivo httpd.conf em sua pasta Xampp (ou Mamp, Lamp, EasyPHP, etc), no Mac fica em /Applications/XAMPP/etc/httpd.conf e edite o arquivo como administrador, caso contrário não será permitido que salve o arquivo.

Por volta da linha 138 você encontrará a seguinte instrução:

User nobody
Group admin

Altere par:

User seu-usuario-no-sistema
Group staff

Vale lembrar que essa alteração foi testada em Mac OS, não testei em Windows e nem Linux, mas acredito que não há diferença no comportamento.

Agora é só testar a atualização dos seus plugins, temas e versões do WordPress.

Utilizando Zend Auth

A Zend Auth serve para autenticar usuários em sua aplicação, ou seja, com ela você pode disponibilizar conteúdos exclusivos somente para quem estiver logado em seu sistema. Ela não distribui níveis de acesso diretamente, você pode configurar esses níveis de acesso e restringir no momento da autenticação ou usar a Zend ACL.

Neste momento não irei criar o formulário de postagem para focar bem na Zend Auth.

Crie dois controllers em sua aplicação, um para login e outro para o logout. Sugiro criar um terceiro controller apenas para testar a autenticação.

No controller dedicado ao login insira

/* Resgata o adaptador do banco de dados utilizado */
$dbAdapter = Zend_Db_Table::getDefaultAdapter();

$authAdapter = new Zend_Auth_Adapter_DbTable(
  $dbAdapter, //adaptador do banco de dados padrão da sua app
  'users', // nome da tabela de usuários
  'usuario', // nome da coluna com o nome do usuário
  'senha', // nome da coluna com o campo da senha
  'sha1(?)' // campo opcional caso a senha seja criptografada
);

/* Opcionalmente você pode capturar estes dados com os setter methods */
$authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter);
$authAdapter->setTableName('users')
            ->setIdentityColumn('username')
            ->setCredentialColumn('password');

/* Estou pressupondo que o login e senha foram capturados nas variáveis $username e $password */
$authAdapter->setIdentity($username)->setCredential($password);

/* Variável que guarda o resultado da tentativa de autenticação */
$result = $authAdapter->authenticate();

/* Agora verificamos se o retorno de $result é verdadeiro ou falso */
if($result->isValid()){
  /* Se verdadeiro inicia uma instância do Zend Auth */
  $auth = Zend_Auth::getInstance();
  /* Resgata os dados do usuário, exceto a coluna senha */
  $data = $authAdapter->getResultRowObject(null,'senha');
  /* Grava a seção no sistema */
  $auth->getStorage()->write($data);
  /* Uma vez que o usuário está logado você pode optar por redirecioná-lo ou exibir alguma mensagem */
  $this->_redirect("/");
} else {
  /* Caso o usuário não tenha sido autenticado por algum motivo você registra aqui que atitude tomar */
  $this->view->mensagem = "falhou";
}

No controller responsável pelo logout é bem mais simples.

/* Inicia uma instância de Zend Auth */
$auth = Zend_Auth::getInstance();
$auth->clearIdentity(); // apaga a identidade / encerra a seção
$this->_redirect("/"); // opcionalmente você pode redirecionar para alguma página ou exibir uma mensagem

Ok, e como fazer para saber se o usuário está logado ou não?

Nas páginas onde deseja restringir o acesso, acesse o seu respectivo controller e insira a função preDispatch.

public function preDispatch()
{
  /* Verifica se o usuário não está logado */
  if(!Zend_Auth::getInstance()->hasIdentity())
    // Se não estiver logado, você redireciona para uma página de erro.
    $this->_redirect("/erro");
}

Trabalhando com relacionamento de tabelas no Zend Db Table

Existe uma facilidade muito grande para se trabalhar com relacionamento de tabelas no Zend Framework, e evita-se aquelas enormes querys para trazer meia dúzia de dados.

Após criar os Models via Zend_Tool

zf create model nome-da-tabela

Abra o model da tabela principal e insira na classe a linha que registra qual a tabela filha:

protected $_dependentTables = array('Application_Model_DbTable_NomeDaTabelaFilha');

Em seguida, abra a tabela filha e registre mapeamento dos campos de relacionamento:

protected $_referenceMap = array(
	//Array das tabelas relacionadas
	'Empresa' => array(
		//Nome da Tabela relacionada
		'refTableClass'=>'Application_Model_DbTable_NomeDaTabelaPai',
		//Campo de relacionamento da tabela pai
		'refColumns'=>'id',
		//Campo de relacionamento da tabela filha
		'columns'=>'empresa_id',
		//Opcionais
		'onDelete'=>self::CASCADE,
		'onUpdate'=>self::RESTRICT
		)
	);

A partir de agora você pode chamar diretamente no seu controller:

$tabela_principal = new NomeDoModel();
$this->view->dados = $tabela_principal->fetchAll();

 E na sua view fazer o tratamento com um “foreach”, a única coisa que deverá ser acrescentada é no ponto onde você quer as informações da tabela filha usar o seguinte comando:

$tabela_secundaria = $tabela_principal->findDependentRowSet('Application_Model_DbTable_NomeDaTabelaFilha');

Isto retorna um array que poderá ser tratado norlmamente com o uso de um foreach.

Configurações opcionais para os módulos de uma aplicação Zend

Após modular a sua aplicação, se faz necessário adaptar algumas coisas. Por exemplo, os diretórios padrão /controllers e /views da sua aplicação não serão mais usados pois agora para qualquer controller e view criado deverá ser apontado o Módulo a qual pertence, ou seja:

zf create controller teste 1 default

onde teste é o controller que quer criar, 1 é o parâmetro para que uma view relacionada seja criada e default é o nome do módulo ao qual o controller se refere. Portanto, se somente depois de concluir a aplicação você decidiu modular, será necessário retrabalhar todos os controllers para obedecer a nova estrutura. Não basta somente mover as pastas para dentro dos novos módulos, será necessário editar o nome da classe de cada controller para abrigar o nome do módulo, ou seja, antes de modular a aplicação a sua classe controller iniciava-se assim:

class IndexController extends Zend_Controller_Action{}

Após a modulação, a mesma classe iniciará desta forma:

class Default_IndexController extends Zend_Controller_Action{}

Onde “Default_ ” é o nome do módulo a qual pertence. Depois de migrar suas classes e views pode remover as respectivas pastas da raiz já que não serão necessárias.

Para obter melhor controle sobre essa transição, existem alguns parâmetros que podem auxiliar.

No seu arquivo Application.ini insira as seguintes linhas:

; Nome do módulo que quer usar como Padrão
resources.frontController.defaultModule = "default"
resources.modules[] =
; Opcionais, somente para o caso de alterar o nome padrão
resources.frontController.defaultController = "index" 
resources.frontController.defaultAction = "index"

Configurando DB Adapter pela Zend Tool

Existem duas formas de configurar o acesso ao seu banco de dados no Zend.

A primeira e mais comum é editar o arquivo /application/configs/Application.ini e inserir as linhas com os dados de acesso ao banco de dados:

resources.db.adapter = "PDO_MySQL"
resources.db.params.host = "localhost"
resources.db.params.dbname = "zf_exemplo"
resources.db.params.username = "root"
resources.db.params.password = "root"

A segunda que considero mais rápida, uma vez que você já tem todos os dados necessários é por meio da Zend Tool. Acesse via terminal a pasta da sua aplicação e digite:

zf configure db-adapter 
"adapter=PDO_MySQL
&host=localhost
&dbname=zf_exemplo
&username=root
&password=root"

Para verificar se os dados foram inseridos corretamente acesse o arquivo de configuração e verá que os dados foram gravados como citado no terceiro parágrafo.

Custom SQL no WordPress

As funções do WordPress são muito úteis e na maioria das vezes o programador não necessita sequer pensar em banco de dados, conexão, select e outros termos que são de uso corrente em uma pesquisa em banco de dados com PHP. Entretanto, mesmo com toda essa facilidade de pesquisa, existem alguns casos em que não é possível escapar de customizar a query para obter os resultados com menor uso de memória e evitar também o timeout da aplicação.

Uma boa referência do que será tratado aqui pode ser visto de forma mais ampla em http://codex.wordpress.org/Class_Reference/wpdb.

Preparei aqui uma busca customizada no banco para pesquisar

$sql = "SELECT wusers.ID,
               wmeta.user_id, 
               wmeta.meta_key, 
               wmeta.meta_value
        FROM 
               $wpdb->users wusers
        INNER JOIN 
               $wpdb->usermeta wmeta
	ON 
                wusers.ID = wmeta.user_id
	WHERE 
                wmeta.meta_key = 'Twitter'
	ORDER BY 
                wmeta.meta_value ASC";

Então estamos pedindo para selecionar os campos ID da tabela users e user_id, meta_key e meta_value da tabela usermeta onde o campo meta_key é igual a Twitter e mandamos ordenar pelo valor. Observe que nesse caso eu estou citando um campo que foi personalizado, mas poderíamos pesquisar por first_name, last_name ou qualquer outro meta_key registrado. Em caso de dúvida consulte o post Melhorando o formulário de cadastro no WordPress.

Tendo a sintaxe SQL, vamos utilizar as funções do próprio WordPress para processar os resultados.

global $wpdb;

$users = $wpdb->get_results($sql);

foreach($users as $user){
  $uinfo = get_userdata($user->ID);  
  echo $uinfo->first_name . " : " . $uinfo->twitter<br />";
}

Com isso podemos gerenciar o cadastro de usuários, podemos fazer uma ficha com foto utilizando o Avatar por meio da função

get_avatar($user->ID);

ou mais completa:

get_avatar( $user->ID, 
            $tamanho = '96', 
            $img_padrao = 'http://www.seusite.com.br/avatar.jpg' )

Também é possível inserir diretamente o link para a imagem

$grav_url = "http://www.gravatar.com/avatar/" . 
             md5(strtolower($user->user_email)) . 
             "?d=" . urlencode($avatar_padrao) . 
             "&s=" . $tamanho;

echo "<img src='$grav_url'/>";

Agora é usar a criatividade e a necessidade para otimizar os seus resultados.

Melhorando o formulário de cadastro do WordPress

Existem uma série de plugins que gerenciam para você o cadastro de usuários no WordPress adicionando ou retirando campos, entretanto diversas vezes os achei muito limitados e até difíceis de gerenciar pelo usuário comum.

O problema que eu vejo em se utilizar plugins que são feitos por terceiros é que você sempre fica à mercê das atualizações promovidas pelo desenvolvedor ou a própria descontinuação do projeto. Então, exceto pelos plugins que são de desenvolvedores ou empresas conhecidas, eu prefiro sempre que possível fazer a minha implementação.

Neste exemplo não construirei um plugin, mas indico como fazer o uso das funções do WordPress para gerenciar o cadastro de usuários.

Primeiramente devemos relacionar o que queremos em nosso formulário.

  • Nome
  • Email
  • Twitter
  • Telefone
  • Endereço
  • Bairro
  • Cidade
  • Estado
  • CEP

A seguir, baseado no formulário de cadastro que o WP disponibiliza vamos personalizar o nosso. Iniciaremos excluindo alguns campos que não são necessários.

add_filter('user_contactmethods','add_contactmethod',10,1);

function add_contactmethod( $contactmethods ) {
  $contactmethods['user_twitter'] = 'Twitter';
  $contactmethods['user_telephone'] = 'Telefone';

  unset($contactmethods['aim']);
  unset($contactmethods['jabber']);
  unset($contactmethods['yim']);

  return $contactmethods;
}

A variável $contactmethods contempla todas os campos referentes às informações de contato. Poderíamos já incluir nesta função todos os campos que queremos como o de endereço e bairro, mas ficaria confuso misturar esse tipo de informação, então para esta situação, iremos criar uma seção exclusiva para estas informações extras. Em resumo, para adicionar campos utilize $contactmethods[‘nome_do_campo’] = ‘Label do Campo’ e para excluir um existente utilize a função do php unset($contactmethods[‘nome_do_campo’]).

Então já temos assim os campos Nome e email (padrão do WordPress) e agora acabamos de adicionar Twitter e Telefone. Vamos agora criar o formulário interno com os novos campos tanto para inserção quanto para atualização de cadastro.

add_action( 'show_user_profile', 'extra_info_profile_fields' );
add_action( 'edit_user_profile', 'extra_info_profile_fields' ); 

function extra_info_profile_fields($user) { ?>
<h3>Informações extras</h3>
  <table>
    <tr>
      <th><label for="user_address">Endereço</label></th>
      <td>
        <input type="text" name="user_address" id="user_address" value="<?php echo esc_attr( get_the_author_meta( 'user_address', $user->ID ) ); ?>" /><br />
        <span>Preencha o seu endereço.</span>
      </td>
    </tr>
    <tr>   
      <th><label for="user_district">Bairro</label></th>
      <td>
        <input type="text" name="user_district" id="user_district" value="<?php echo esc_attr( get_the_author_meta( 'user_district', $user->ID ) ); ?>" /><br />
        <span>Preencha com o seu bairro.</span>
      </td>
    </tr>
    <tr>
      <th><label for="user_city">Cidade</label></th>
      <td>
        <input type="text" name="user_city" id="user_city" value="<?php echo esc_attr( get_the_author_meta( 'user_city', $user->ID ) ); ?>" /><br />
        <span>Preencha com a sua cidade. </span>
      </td>
    </tr>
    <tr>
      <th><label for="user_state">Estado</label></th>
      <td>
       	<input type="text" name="user_state" id="user_state" value="<?php echo esc_attr( get_the_author_meta( 'user_state', $user->ID ) ); ?>" /><br />
        <span>Preencha com o seu estado. </span>
      </td>
    </tr>
    <tr>
      <th><label for="user_zipcode">CEP</label></th>
      <td>
        <input type="text" name="user_zipcode" id="user_zipcode" value="<?php echo esc_attr( get_the_author_meta( 'user_zipcode', $user->ID ) ); ?>" /><br />
        <span>Digite o seu CEP. </span>
      </td>
    </tr>
 </table>

<?php }
add_action( 'personal_options_update', 'save_extra_info_profile_fields' );
add_action( 'edit_user_profile_update', 'save_extra_info_profile_fields' ); 

function save_extra_info_profile_fields( $user_id ) {
  if ( !current_user_can( 'edit_user', $user_id ) ) {
    return false;
  } 
  update_usermeta( $user_id, 'user_address', $_POST['user_address'] );
  update_usermeta( $user_id, 'user_district', $_POST['user_district'] );
  update_usermeta( $user_id, 'user_city', $_POST['user_city'] );
  update_usermeta( $user_id, 'user_state', $_POST['user_state'] );
  update_usermeta( $user_id, 'user_zipcode', $_POST['user_zipcode'] );
}

A função criada abaixo faz uso da função do WordPress wp_insert_user, que serve exclusivamente para criar o cadastro do usuário, mas ela só responde pelos campos padrão e pelos campos criados por meio do hook user_contactmethods. E os outros campos? Caso a função seja concluída, ela nos retorna o ID do usuário cadastrado, pegamos esse ID e mandamos atualizar o cadastro. Para mais informações sobre esta função acesse http://codex.wordpress.org/Function_Reference/wp_insert_user e para a função de atualizar o cadastro http://codex.wordpress.org/Function_Reference/wp_update_user.

function criar_usuario($firstname, $lastname, $nickname, $user_nicename, $user_email, $display_name, $user_site, $user_blog, $user_twitter, $user_telephone, $user_login, $user_pass, $user_role="subscriber"){
    $user_data = array();
    $user_data["first_name"] = $firstname;
    $user_data["last_name"] = $lastname;
    $user_data["nickname"] = $nickname;
    $user_data["user_nicename"] = $user_nicename;
    $user_data["user_email"] = $user_email;
    $user_data["display_name"] = $display_name;
    $user_data["user_url"] = $user_site;
    $user_data["user_blog"] = $user_blog;
    $user_data["user_twitter"] = $user_twitter;
    $user_data["user_telephone"] = $user_telephone;
    $user_data["user_login"] = $user_login;
    $user_data["user_pass"] = $user_pass;
    $user_data["role"] = $user_role;

    return  wp_insert_user( $user_data );
    update_user();
}
function update_user($user_id, $address, $district, $city, $state, $zipcode){
  if(!is_wp_error($user_id)){
    update_user_meta( $user_id, 'user_address', $address);
    update_user_meta( $user_id, 'user_complement', $complement);
    update_user_meta( $user_id, 'user_district', $district);
    update_user_meta( $user_id, 'user_city', $city);
    update_user_meta( $user_id, 'user_state', $state);
    update_user_meta( $user_id, 'user_zipcode', $zipcode);
  }
}

Para efetivar o seu cadastro, basta criar o seu formulário e na página de processamento chamar as funções acima criadas com as suas respectivas variáveis preenchidas.

 

Plugins Sugeridos

Register Plus
http://wordpress.org/extend/plugins/register-plus/

Simplr Registration Form
http://wordpress.org/extend/plugins/simplr-registration-form/

Register Plus Redux
http://wordpress.org/extend/plugins/register-plus-redux/