Como mostrar o último post de cada categoria no WordPress?

O WordPress é um dos meus programas preferidos e um dos que mais consome as madrugadas já faz alguns anos. Escrevi inúmeros temas, alguns plugins (um único genérico o suficiente para ser público) e já modifiquei algumas partes do código (embora hoje seja raro isso ser necessário).

Acho muito simples e me divirto ao programar em PHP para a web (talvez porque eu faça isso há uns dez anos). Gosto muito da forma como o WordPress é escrito e, com efeito, seu lema é Code is poetryCódigo é poesia. Sua documentação e seu código são muito didáticos e foram, assim como seus temas e as tabelas no banco de dados, evoluindo de acordo com o tempo: as atualizações sempre têm novas features e formas mais genéricas e mais elegantes de fazer as coisas.

O WordPress é um software livre usado por mais de 15% da web (um número incrível!) e tem uma comunidade que produz várias extensões (temas e plugins). Gosto tanto dele que certa vez (no final de 2007) escrevi um plugin só para ganhar uma camiseta (que é tamanho G e ainda assim uso de vez em quando).

Propaganda e blogagem a parte, me deparei com um problema interessante em um dos sites que administro com ele no último fim de semana (a gente sempre se depara com problemas interessantes no WordPress também): dado uma categoria com várias subcategorias, gostaria de mostrar um link para o último post de cada uma de suas subcategorias na página inicial.

Uma solução trivial seria fazer uma query pedindo os filhos de uma dada categoria (usando a tabela wp_term_taxonomy) e uma query por categoria para descobrir seu último post (usando as tabelas wp_posts e wp_term_relationships). Suponha (até o final desse post) que a categoria-mãe de todas as categorias que eu quero mostrar na página seja a de ID 33. Então, essa solução seria algo como:

<?php
$query = mysql_query("SELECT term_id FROM wp_term_taxonomy
                      WHERE parent = 33 AND taxonomy = 'category'");
while (list($cid) = mysql_fetch_row($query)) {
    $posts_query = mysql_query("SELECT p.ID, p.post_name, p.post_title
                                FROM wp_posts AS p,
                                     wp_term_relationships AS r
                                WHERE p.ID = r.object_ID AND r.term_taxonomy_id = '$cid'
                                ORDER BY p.post_date DESC
                                LIMIT 1");
    if (mysql_num_rows($posts_query)) {
        list($id, $permalink, $title) = mysql_fetch_row($posts_query);
        // Faça o que quiser com o post aqui
    } else {
        echo "A categoria $cid não tem posts.\n";
    }
}
?>

Não deve ser difícil de entender, mas dá pra resolver de forma ainda mais simples que essa. O WordPress é fantástico e usar as funções dele próprio é bem mais simples, genérico e resolve o problema. A função get_categories aceita um monte de parâmetros, mas só precisamos do child_of:

<?php
foreach (get_categories('child_of=33') as $cat) {
    list($post) = get_posts("numberposts=1&category={$cat->term_id}");
    // Faça o que quiser com o post aqui
}
?>

(quem entrou no post procurando a solução pro problema pode parar por aqui se não for nerd)

Porém, eu queria resolver o problema com uma única query. Achei que seria mais elegante resolver o problema todo no banco de dados sem escrever em PHP e achei que poderia ficar mais rápido. Acho que não ficou mais elegante e não faço ideia se fica mais rápido (fiquei com a impressão de que seja pior porque faço JOIN de quatro tabelas enormes), nem acho que tenha volume de dados (ainda) no site em que implementei isso pra realmente me preocupar, mas me diverti fazendo. Então segue o resultado:

SELECT p.ID AS id,
       CASE WHEN (p.post_date > DATE_SUB(CURDATE(), INTERVAL 1 MONTH)) THEN
           p.post_title
       ELSE
           ''
       END AS title,
       GROUP_CONCAT(c.slug) AS cat
FROM wp_posts AS p
INNER JOIN
    (
        SELECT MAX(p.post_date) AS post_date, c.term_ID AS cid, COUNT(p.ID) AS count FROM
            wp_posts AS p,
            wp_term_relationships AS r,
            wp_terms AS c
        WHERE
            p.ID = r.object_ID AND
            c.term_ID = r.term_taxonomy_ID AND
            p.post_status = 'publish' AND
            p.post_type = 'post'
        GROUP BY c.term_id
    ) AS last ON p.post_date = last.post_date
INNER JOIN wp_term_relationships AS r ON p.ID = r.object_ID
INNER JOIN wp_terms AS c ON c.term_id = r.term_taxonomy_ID
INNER JOIN wp_term_taxonomy AS t ON t.term_id = r.term_taxonomy_ID
WHERE
    c.term_id = last.cid
    AND t.parent = 33
    AND t.taxonomy = 'category'
    AND p.post_status = 'publish'
    AND p.post_type = 'post'
    # AND last.count >= 3
GROUP BY p.ID ORDER BY p.post_date DESC, last.count DESC;

A query (que na verdade é duas) ordena o resultado por data, retorna o título vazio caso o post seja de mais de um mês atrás, junta as categorias (separando-as por vírgula) se um mesmo post for o último de mais de uma categoria e neste caso ordena as categorias por ordem decrescente de número de posts na mesma.

(A parte comentada seria para caso eu só quisesse mostrar o último post de categorias com três ou mais posts.)

Usei o resultado da query da seguinte forma:

<?php
$q = mysql_query($query); // $query é a string com aquele SQLzão
$print_final_li = false;
$first = true;
while ($a = mysql_fetch_array($q, MYSQL_ASSOC)) {
    echo "\t\t<li>\n";
      $permalink = get_permalink($a["id"]);
      $title = $a["title"];
      if ($title == "") {
          $print_final_li = true;
          echo "Veja também: ";
      } else if ($first == true) {
          echo "<a href="$permalink" title="$title">";
          echo get_the_post_thumbnail($a["id"], "home-thumbnail",
                                      Array("title" => get_the_title()));
          echo "</a> ";
          $first = false;
      }
      $cats = explode(",", $a["cat"]);
      foreach ($cats as $low) {
          $up = strtoupper($low);
          echo "<a class="cat" href="http://$low.juntos.org.br/"
                   title="Juntos! $up">$up</a> ";
      }
      if ($print_final_li) break;
      echo "<a class="post" href="$permalink" title="$title">$title</a>";
      echo "</li>\n";
}
while ($a = mysql_fetch_array($q, MYSQL_ASSOC)) {
    $cats = explode(",", $a["cat"]);
    foreach ($cats as $low) {
        $up = strtoupper($low);
        echo "<a class="cat" href="http://$low.juntos.org.br/"
                 title="Juntos! $up">$up</a> ";
    }
}
if ($print_final_li) {
    echo "</li>\n";
}
?>

(e se você quiser vê-lo em prática, entre em juntos.org.br e procure por “Juntos pelo Brasil”)

Não ficou bonitinho? Se por um lado gostei da solução, por outro fiquei imaginando que deva ser um SQL tremendamente ingênuo e digno da minha inexperiência com grandes bancos de dados. O que você acha? Consegue pensar numa forma mais simples, mais eficiente e mais elegante de resolver o mesmo problema? Ou ao menos sem subqueries?

Acho que as relações necessárias já ficaram explícitas na query que eu escrevi, mas segue o diagrama do banco de dados do WordPress pra quem precisar:

Diagrama do banco de dados do WordPress

Publicidade gratuita

Alguém quase sem nada pra fazer resolveu mandar o Lynx baixar todas as médias do ENEM, rodou um programa em PHP com expressões regulares para separar os valores e agora apresenta-lhes a escola com a melhor média de Santa Catarina:

mysql> select id, nome, media from enem where estado='sc' order by media desc limit 40;
+-----+-------------------------------------------+-------+
| id  | nome                                      | media |
+-----+-------------------------------------------+-------+
| 185 | COLEGIO SALESIANO ITAJAI                  |  6456 |
| 821 | INST SUP E CENTRO EDUC LUTERANO BOM JESUS |  6435 |
| 140 | COLEGIO ENERGIA                           |  6295 |
|  49 | CENTRO EDUC ENERGIA TUBARAO SC LTDA       |  6209 |
|  87 | COLEGIO BOM JESUS DIOCESANO               |  6167 |
| 154 | COLEGIO HENRY FORD                        |  6144 |
| 193 | COLEGIO SAO BENTO                         |  6144 |
| 834 | SOCIEDADE EDUC POSIVILLE LTDA             |  6085 |
| 166 | COLEGIO MARISTA SAO LUIS                  |  6073 |
| 142 | COLEGIO ENERGIA                           |  6060 |
| 813 | EXATHUM CURSO E COLEGIO                   |  6034 |
| 116 | COLEGIO CORACAO DE JESUS                  |  5984 |
|  48 | CENTRO EDUC ENERGIA SC LTDA               |  5959 |
|  68 | CENTRO FEDERAL DE ED TECNOLOGICA DE SC    |  5936 |
| 173 | COLEGIO MURIALDO                          |  5909 |
| 115 | COLEGIO CONSUL CARLOS RENAUX              |  5899 |
| 206 | COLEGIO TENDENCIA                         |  5885 |
| 128 | COLEGIO DEHON                             |  5872 |
|  76 | COLEGIO ALTO VALE LTDA                    |  5861 |
|  88 | COLEGIO BOM JESUS STO ANTONIO             |  5852 |
| 365 | EEB FELICIANO NUNES PIRES                 |  5843 |
| 119 | COLEGIO DE APLICACAO DA UNC CACADOR       |  5831 |
| 150 | COLEGIO EXPONENCIAL                       |  5811 |
| 182 | COLEGIO RAINHA DO MUNDO                   |  5796 |
| 183 | COLEGIO SAGRADA FAMILIA                   |  5788 |
| 160 | COLEGIO MAFRENSE                          |  5779 |
|  96 | COLEGIO CENECISTA DR JULIO CESAR R NEVES  |  5777 |
| 210 | CONJ EDUC DR BLUMENAU                     |  5761 |
|  51 | CENTRO EDUC FRAIBURGO CEFRAI              |  5746 |
| 184 | COLEGIO SAGRADA FAMILIA                   |  5746 |
| 164 | COLEGIO MARISTA FREI ROGERIO              |  5724 |
|  69 | COLEGIO ADV DE  INDAIAL                   |  5721 |
| 196 | COLEGIO SAO LUIZ                          |  5720 |
| 159 | COLEGIO MADRE TERESA MICHEL               |  5719 |
| 801 | ESC BARAO DO RIO BRANCO                   |  5717 |
| 835 | SOCIEDADE EDUC VERDES MARES               |  5696 |
| 211 | COOPERATIVA EDUCACIONAL MAGNA             |  5688 |
| 133 | COLEGIO DOM JAIME CAMARA                  |  5683 |
| 139 | COLEGIO ELISA ANDREOLI                    |  5681 |
| 134 | COLEGIO DOS SANTOS ANJOS                  |  5673 |
+-----+-------------------------------------------+-------+
40 rows in set (0,00 sec)

A melhor escola possui também o melhor site, que deve ter sido feito por alguém realmente muito bom. ;)

Volta para Casa

Depois de uma semana em Florianópolis, estou novamente em Itajaí. Voltei anteontem no final da tarde e o motivo de não ter postado nada é que eu tinha que acabar um trabalho, o site da BEMFAM: tableless, padrões web, cross-browser, PHP/MySql, totalmente administrável. Esse foi o segundo serviço que eu fiz para a Meetweb, depois da Coalizão Antituberculose. Esse ano estou deixando de trabalhar fora (no Colégio) para pegar esses sites de fora que me rendem um pouco mais e são bem mais legais de se fazer (principalmente quando trabalho com designers).

Na semana em Floripa, além de ir à práia, li três livros que merecem ser sugeridos: o best-seller O Código da Vinci, de Dan Brown; Fortaleza Digital, também de Dan Brown; e Harry Potter e o Enigma do Príncipe, o sexto livro de J. K. Rowling. O estilo do Dan Brown é muito legal. Além de ser um cara extremamente inteligente, criando enigmas muito interessantes durante o livro, ele consegue prender a atenção do leitor de forma incrível. Gostei bastante… Agora estou querendo comprar o seu outro livro, Anjos e Demônios. Ou se alguém quiser me presentear, ficarei grato. :D

Passei o ano novo em um lugar de Laguna chamado “Mato Alto”, uma região que, segundo meus cálculos, está no mínimo uns 10 anos atrasada no tempo. :) O bom é que num lugar desses, não se tem nada pra fazer e então eu aproveitei pra ficar lendo sem stress o livro Algoritmos: Teoria e Prática e; aliás, fiz alguns testes inúteis, como descobrir o custo dos algoritmos de ordenação se forem executados por pessoas. Os resultados são bem interessantes. Por exemplo, o Insertion Sort é MUUUITO mais rápido que o Merge Sort, já que você põe todas as cartas que já foram empilhadas na sua mão e com a outra simplesmente coloca uma carta no meio do bolo. O Merge Sort já é bem mais difícil porque você precisa fazer pilhinhas na mesa… :)

Nessa semana, vou tentar pegar o PhotoX, mas não muito intensamente, porque estou agora mais tranquilo lendo e estudando de leve.

Ah… Finalmente o desktop de minha casa tem um novo HD. Foi uma troca boa… Um HD corrompido de 20gb por um de 80gb… :D Agora tenho um bom lugar pra backups! Já tá particionado pra Linux e pra Windows.

Vou começar a postar alguns artigos explicativos, pequenos tutoriais, sobre coisas simples para quem está iniciando em algoritmos computacionais, PHP, tableless, Linux, etc. Tenho recebido vários e-mails pedindo sugestões de apostilas e essas coisas de onde começar e acho que seria legal até pra aumentar as visitas do meu site, meu pagerank, e conseqüentemente, o dinheiro do Google Adsense. :D Aliás, você já acessou meu site usando Internet Explorer? Acho que o negócio que eu fiz foi um dos mais legais pra ganhar uns trocados com o Firefox / Google Toolbar.

Álbum de Fotos

Link para projeto em desenvolvimento…

Meu Flickr tá estourando (depois de 200 fotos, começam a sumir fotos segundo seu FAQ) e por isso resolvi criar um álbum de fotos pessoal. Estou desenvolvendo em PHP, usando um banco de dados MySql e estou criando bastante recursos Ajax para exercitar um pouco e para o negócio ficar bem dinâmico (se estiver ficando muito exagerado, me avisem!). Este projeto que ainda não tem nome, mas que estou pensando em algo como PhotoX (gostou do nome? comente! não gostou? comente também!), deve ter todos os recursos do Flickr (tipo, All Sizes, Notes e Rotate) e o que surgir de idéias legais. Será um software livre, cada um instala em seu servidor (ex.: é um “WordPress“, não um “Blogger“) e por isso ele não tem limites de sets, tags, fotos, tamanhos ou qualquer coisa do tipo. Irá requerer PHP 4.3, está sendo desenvolvido usando classes (estou tentando exercitar programação orientada a objetos), usa a biblioteca GD (para trabalhar com as imagens) e é totalmente Web 2.0 (tableless, padrões HTML 4.01 Strict, Ajax, tagsonomia, simplicidade). Já estou o criando multi-linguagem, ele funciona com alguma coisa parecida com templates e deve sair em no máximo um mês.

Estou convidando programadores sem nada pra fazer pra me dar uma ajuda (claro que gratuita). O Gustavo é uma das pessoas que me deu uma ajuda fazendo um pedaço da classe Foto e da classe Comentario e aqui estendo o convite para qualquer pessoa que lê o meu blog e queira ajudar. O sistema é simples: eu te dou um login e senha no meu FTP e você desenvolve o que você conseguir (postando sempre que você muda uma letra o novo resultado, para que depois outra pessoa pegue e possa continuar).

Atualizado

O Renato deu uma idéia legal aí num comentário que é hospedar o troço em alguns desses sites de projetos de software livre e usar CVS pro desenvolvimento. Acho que realmente faz sentido, eu não tinha pensado nisso… Hehehe… Vou criar algo a respeito e depois eu publico aqui!

Se você for uma dessas pessoas dispostas, gostaria de pedir que você note alguns detalhes na construção dos meus arquivos:

  • Tabulação é feita com “tabs”.
  • As classes não imprimem nada na janela.
  • Mesmo os arquivos não imprimem nada também, eles imprimem para a variável $buf.
  • Não vale mexer nos arquivos config.php, index.php, ajax.php, scripts.js.php e style.css (por favor, deixe toda a parte de client-side, Ajax e configurações globais para mim :D ).
  • Todas as coisas que você passar para a variável bufna~opodemcontertexto.(Sevoce^querescreverqualquercoisaaleˊmdoquefoiretornadodobancodedados,devecriarumavariaˊvelbuf não podem conter texto. (Se você quer escrever qualquer coisa além do que foi retornado do banco de dados, deve criar uma variávelLANG[‘NOMEDAVAR’] no arquivo lang/pt_BR.php e lang/en_US.php
  • Se você não souber programar mas estiver afim de traduzir o projeto para alguma língua, me dê seu nome que quando tiver pronto eu vou querer muito sua ajuda.
  • Se você não quiser traduzir e nem souber programar, colabore com idéias de coisas que você acha legal o projeto ter (o que falta no Flickr que seria legal os programadores colocarem, ou sei lá…)

Espero que todos tenham entendido o espírito. Me mandem e-mail com sugestões e quem puder ajudar, ajude. Quem quiser dar um nome ao projeto, pode me sugerir também! Tenho certeza que um software livre desenvolvido pela comunidade para um fim que ainda não existe algo parecido (alguém conhece algum software livre de álbum de fotos que faça tudo que o Flickr faz?) fará bastante sucesso e será bem aceito ao menos pelos programadores (grande parte deles usa o Flickr mas tem um servidor legal que suporta PHP e GD).


Agora vamos voltar ao blog.

Fiquei um tempo sem postar justamente por causa desse projeto, que estou me esforçando para fazer o mais rápido possível. Também estou lendo “Java – Como Programar”. Tô gostando bastante da didática e gostando também da linguagem Java. No mais, não estou fazendo muita coisa. Estou indo trabalhar todos os dias a tarde, viajei final de semana para Florianópolis e agora que meu primo foi embora, minha casa está bem vazia (o que é ótimo! :) )

Troca de Servidor e “Semantic Blog”

Em primeiro lugar, quero pedir desculpa a quem entrou no meu site hoje e teve algum problema. Acontece que acabo de trocar de servidor. Depois de ver uma propaganda no Tableless.com.br, acessei o site da NerdHost e gostei dos preços e da qualidade do serviço. Por causa desses fatos e por eles estarem incentivando os padrões web (dão um mês gratuito pra quem desenvolve em tableless), fechei o contrato com eles no dia em que eu vi. :) Esse servidor usa CPanel, que eu acho bem melhor que o painel de controle da Metaweb e tem vantagens como um SSH que funciona SCP (acreditem! O da Metaweb não funcionava!), subdomínios ilimitados, PHP 5, MySql 4… Foi uma excelente troca! :D E ainda peguei o plano mais barato (limpei o servidor, consegui deixá-lo com 60mb), que custa R$ 5,67 (incrível a precisão do valor… hehehe).

Em segundo lugar: Eu, o Hélio e o Gustavo começamos a desenvolver um sistema de blog, em cima de classes, bastante parecido com o meu mas mais simples que tem como objetivo ser leve e possuir apenas o que é necessário. Ele já vai vir com um Shortstat bem modificado pra dar várias estatísticas (tipo, comentários por visitante, umas paradas assim) e com o GeSHi Highlighter. Os posts vão usar BBCodes e o blog vai ser baseado em templates. Resolvemos dar o nome “Semantic Blog”. Gostou? Deixe um comentário pra eu saber! Não gostou? Deixe um comentário também! :p Hehehe… Se você tiver qualquer sugestão de recurso interessante que ele deve ter, também fique a vontade para postar um comentário!

© 2005–2020 Tiago Madeira