Mostrando uma agenda do Google Calendar no seu site

Escrito em PHP. Pode ser usado no tema do seu WordPress. Requer CURL. Faz cache do calendário para não ter que baixá-lo sempre que alguém entra no seu site. Desenvolvido para um site que vai sair nos próximos dias. Use, modifique e distribua como quiser. (Não me responsabilizo por qualquer problema. Fiz pra um caso específico. A checagem de erros é meio porca.)

<?php
date_default_timezone_set('America/Sao_Paulo');
$events = Array();
$dom = new DOMDocument();

$file = "cached_calendar.xml";

$last = -1;
if (file_exists($file)) {
    $last = filemtime($file);
}
// Mude 3600 para o tempo (em segundos) que você quiser que o cache expire
if (time() - $last > 3600) {
    $fp = fopen($file, "w+");
    if (!$fp) {
        die();
    }
    // Substitua o e-mail do calendário do Google CodeJam pelo e-mail do seu calendário (público)
    $ch = curl_init("https://www.google.com/calendar/feeds/" .
      "google.com_jqv7qt9iifsaj94cuknckrabd8%40group.calendar.google.com/public/full");
    curl_setopt($ch, CURLOPT_TIMEOUT, 50);
    curl_setopt($ch, CURLOPT_FILE, $fp);
    curl_exec($ch);
    curl_close($ch);
    fclose($fp);
}

$dom->load($file);
$feed = $dom->getElementsByTagName("feed");
$entries = $feed->item(0)->getElementsByTagName("entry");
foreach ($entries as $entry) {
    $children = $entry->getElementsByTagName("*");
    $day = "";
    $start = "";
    $end = "";
    foreach ($children as $child) {
        switch ($child->tagName) {
        case "title":
            $title = $child->nodeValue;
            break;
        case "gd:when":
            if ($child->hasAttribute("startTime")) {
                $st = strtotime($child->getAttribute("startTime"));
                $time_to_sort = $st;
                $day = date_i18n("l, d/M", $st);
                $start = date("H:i", $st);
            }
            if ($child->hasAttribute("endTime")) {
                $et = strtotime($child->getAttribute("endTime"));
                $end = date("H:i", $et);
            }
            break;
        }
    }
    if ($title != "" && $day != "") {
        $events[] = Array(
            "time_to_sort" => $time_to_sort,
            "day" => $day,
            "start" => $start,
            "end" => $end,
            "title" => $title
        );
    }
}
function cmp($a, $b) {
    $a = $a["time_to_sort"];
    $b = $b["time_to_sort"];
    if ($a == $b) {
        return strcmp($a["title"], $b["title"]);
    }
    return ($a < $b) ? -1 : 1;
}
usort($events, "cmp");

$n = count($events);
if ($n > 0) {
    $lastDay = "";
    for ($i = 0; $i < $n; $i++) {
        $day = $events[$i]['day'];
        $title = $events[$i]['title'];
        $start = $events[$i]['start'];
        $end = $events[$i]['end'];
        if ($lastDay != $day) {
            if ($i != 0) {
                echo "</ul>\n\n";
            }
            echo "<h3 class="day"><span>$day</span></h3>\n";
            echo "<ul>\n";
          }
          echo "\t<li>\n";
            if ($start != "") {
                // Você pode modificar aqui para mostrar o horário de término ($end).
                echo "tt<span class="time">{$start}</span>\n";
            }
            echo "\t\t<strong>{$title}</strong>\n";
            echo "\t</li>\n";
          $lastDay = $day;
      }
      echo "</ul>\n";
} else {
    echo "<p>Nenhum evento cadastrado.</p>\n";
}
?>

WordPress plugin: Admin Anti-forget Alarm

I wrote a small WordPress plugin to prevent users from publishing a post without excerpt or thumbnail, or with a too big excerpt, or with a too small thumbnail, or with an uppercase-only title.

Screenshot do plugin

This screenshot is showing some of the messages the plugin displays in portuguese.

For some of my websites it’s important to require the editors to fix some stuff before publishing something, and it looks like this is a useful feature for other people as well. There is even a Require Thumbnail plugin in the WordPress Plugin Directory that seems to do one of the things I’ve just implemented.

The plugin works with two different types of requirements: ths first generates errors (i.e., you can’t publish if you don’t fix it) and the second generates warnings (i.e., you will receive a message but you can proceed to publish if you really want to do that).

I thought of not releasing the plugin (because it’s written in Portuguese and you don’t have a cool interface to decide what’s required yet), but in a fashion of overstated bazaar I decided to push the code anyway (without putting in the WordPress plugin directory, of course) so that other people can collaborate if they want to. Take a look :)

Github project home page: https://github.com/tmadeira/antiforget

Git repository to clone: https://github.com/tmadeira/antiforget.git

Code (PHP): https://github.com/tmadeira/antiforget/blob/master/antiforget.php

Download: antiforget.zip (this is pre-alpha: I provide no warranty!)

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

Retrospective: new plugin for WordPress

I’ve just wrote my first WordPress public plugin, that I’m licensing under GPL v3. This post is to make it public. I’m writing in English because the WordPress plugin directory asks me to provide a plugin page to host the files there and I’ll provide the URL of this post. Update: The plugin is now in the WordPress plugin directory: wordpress.org/extend/plugins/retrospective/


The website of the brazilian newspaper O Estado de São Paulo has a nice way to display news in a retrospective-style (check this screenshot or this link — Flash required).

Wouldn’t it be nice if we could display WordPress posts in our pages and categories in the same way just by using a shortcode? The possibilities are many. That’s why I wrote the Retrospective plugin for WordPress.

It has at least two advantages over the version you just saw:

  1. Does not require Flash (its implementation uses only CSS and JavaScript/jQuery)
  2. Has a option to respect the (time-)scale of the posts.

Its use is very simple. Wherever you add the shortcode [retrospective] the plugin will draw that cool retrospective. The shortcode supports several attributes:

  • count — limit the number of posts to be displayed (default = 10; use -1 to display all)
  • cat — display posts with category IDs comma-separated (default = display all posts)
  • width — the width of the timeline in pixels (default = 600)
  • delay — the time of the focus change animation in milisseconds (default = 1000)
  • scale — if set, respect the time scale in the distances between the points in the timeline (default = false)
  • image_width, image_height — the dimensions of the thumbnail images in pixels (default = 300×180)
  • image_border_size — the size of the image’s border in pixels (default = 7)
  • image_border_color — the color of the image’s border in hexa RGB (default = 000000)
  • image_margin — the space between the images (default = 5)
  • date_format — the date format in PHP format (default = d/m/Y)

Some screenshots

Here is a screenshot from juntos.org.br with scale=true (in the link you can see it working):

Screenshot (Retrospective plugin in juntos.org.br)

And here is a screenshot from a fresh WordPress install (TwentyEleven theme without modifications):

Screenshot (Retrospective plugin in TwentyEleven theme

Customizing

Post thumbnails

For better results, I suggest always adding post thumbnails to your posts and using registered image sizes in image_width and image_height attributes.

Styling retrospectives

The generated HTML is very easy to style (but just be careful with margins and paddings, they’re set with !important attribute — I did it to try not to break with any theme). Here is a sample:

<div id="retro-uniquehash" class="retrospective">
  <!-- TIMELINE -->
  <ul class="time">
    <li rel="0">
      <a href="permalink" style="left:0px;"><span>date</span></a>
    </li>
    <li rel="1">
      <a href="permalink" style="left:300px;"><span>date</span></a>
    </li>
    <li rel="2">
      <a href="permalink" style="left:600px;"><span>date</span></a>
    </li>
  </ul>

  <!-- PHOTOS -->
  <div class="photos">
    <ul>
      <li rel="0">
        <a href="permalink" title="title"
          ><img src="file" class="wp-post-image"
        /></a>
      </li>
      <li rel="1">
        <a href="permalink" title="title"
          ><img src="file" class="wp-post-image"
        /></a>
      </li>
      <li rel="2">
        <a href="permalink" title="title"
          ><img src="file" class="wp-post-image"
        /></a>
      </li>
    </ul>
  </div>

  <!-- POSTS -->
  <ul class="posts">
    <li rel="0">
      <a href="permalink" title="title">
        <h2>Title <span>(date)</span></h2>
        <p>Excerpt</p>
      </a>
    </li>
    <li rel="1">
      <a href="permalink" title="title">
        <h2>Title <span>(date)</span></h2>
        <p>Excerpt</p>
      </a>
    </li>
    <li rel="2">
      <a href="permalink" title="title">
        <h2>Title <span>(date)</span></h2>
        <p>Excerpt</p>
      </a>
    </li>
  </ul>
</div>

Styling a specific retrospective

The generated hash takes in consideration all the attributes sent to the shortcode and also how many retrospectives appeared before in the parsing of the actual page. I made it that way to allow users to set up two exactly equal retrospectives in the same page. Because of that, I don’t recommend setting styles for #retro-uniquehash. I think a reasonable solution for this issue is to make add an outer container.

Download

Here is the code for download: retrospective.zip

* Warning: Please consider I’m using a bazaar approach here. Be aware that the plugin probably has a lot of bugs (and please tell me if you catch any).

I hope you enjoy it. Have fun and please let me have your feedback! :)

Problemas ao comentar?

Duas pessoas reportaram que este blog não está aceitando comentários. Eu acabei de testar e não tive problemas, porém acho estranho que ele esteja há cinco dias sem receber comentários (ou nem tanto, já que as pessoas também vivem fora da internet e os últimos quatro dias foram de Páscoa) e duas pessoas diferentes não conseguiram comentar.

Se você não está conseguindo comentar, favor deixar um comentário para eu investigar o problema.

No mais, estou temporariamente sem internet em casa (e espero que ela volte hoje), motivo pelo qual estou ausente na internet.

© 2005–2020 Tiago Madeira