Journal tag: wordpress

Friday, 9th August 2019

Tag links in WordPress category archives

The goal

In a WordPress category archive page, show a list of tags used within those posts. Each tag links to an archive page showing tagged posts with category matching the referring archive. e.g. show all posts tagged WordPress in Journal. The page URL needs to be human-friendly e.g. /[category]/[tag]/.

The problem

This site uses the standard WordPress post type for Journal and Links. Posts are differentiated by use of a namesake category:

WordPress post admin section

It’s now straightforward to use the WordPress URL hierarchy to show category archives, splitting out the two content types into their own pages. To show an accompanying list of tags, instinctively I may opt for the WordPress function get_tags(). However, this will get tags from all posts regardless of category. In the Journal archive tag list, tags are included that are used by Links but not Journal, and visa-versa.

Further to this issue, the tags link to the standard tag archive page. It’s likely that a Links post and a Journal post will share tags, which results in the tag archive displaying a mix of content types.

The solution

  1. Use a custom query to get all posts with the same category as the archive.
  2. Loop through those posts and extract all the tags used.
  3. Populate an array of found tags.
  4. Create a new array that removes duplicate tags so we’re left with an array of unique tags.
  5. Loop through the array of unique tags to output a list of tag links.

I use Timber on this website so the code examples are flavoured accordingly. In archive.php:


 * List tags used within category
if ( is_category() ) {
    // get category of archive
    $category = get_category( get_query_var( 'cat' ) );

    // query posts with relevant category
    $args = array(
        'posts_per_page' => -1,
        'category_name' => $category->slug,

    $the_query = new WP_Query($args);

    $tags = array();

    // loop through posts and extract all tags found
    if ($the_query->have_posts()) {

        while ($the_query->have_posts()) { 

            $post_tags = get_the_tags();
            if ( $post_tags ) {
                foreach( $post_tags as $tag ) {
                    $tags[] = $tag->slug; 


        // extract unique tag IDs
        $unique_tags = array_unique($tags);

        // pass tags used within posts of category archive
        $context['tags'] = $unique_tags;

        // pass slug of archive category
        $context['cat'] = $category->slug;

        if ( $params['tag'] ) {

            // get name of tag using slug in URL 
            $term = get_term_by( 'slug', $params['tag'], 'post_tag');
            $tag = $term->name; 

            // pass page title of category and tag
            $context['title'] = $category->name . ' tag: ' . $tag;

As well as details of the category in question, an array of unique tags is passed to archive.twig for rendering:

<h1>{{ title }}</h1>


<ul class="list">
  {% for tag in tags|sort %}
      <a href="{{site.url}}/{{cat}}/{{ Term(tag).slug }}">{{ Term(tag).name }}
  {% endfor %}

Here, the tags are looped through and sorted alphabetically by the Twig sort filter. The link is formed from a combination of the archive category and tag name for a custom hierarchical link e.g. Timber’s Term class makes light work of getting the tag’s pretty name for display purposes.

The link will 404 as it stands because /[category]/[tag]/ doesn’t appease any WordPress URL structure. This is where Timber’s special powers come into play. Through use of Timber routing, we can get Timber to interpret the new URL structure and use our template of choice to display the results:


 * Implement custom URLS with Timber routing
Routes::map('journal/:tag', function($params){
    $query = 'category_name=journal&tag='.$params['tag'];
    Routes::load('archive.php', $params, $query, 200);

Routes::map('journal/:tag/page/:pg', function($params){
    $query = 'category_name=journal&tag='.$params['tag'].'&paged='.$params['pg'];
    Routes::load('archive.php', $params, $query);

// Do the same for 'Links'...

Timber reads the URL segments and enables their use in a new custom query. So, instead of, we get to use Brilliant! The faux hierarchical relationship between categories and tags is maintained and archive.php is used to show the results as normal.

Wednesday, 24th July 2019

Making notes

It’s only recently that I’ve started paying attention to the IndieWeb movement (is it referred to as a movement?). It feels right. Though I work with the web every day, I’ve become so embeded in the coporate web that I lost sight of what else it can be.

To get started making notes, I looked to Jeremy Keith’s website for a blueprint; the mind boggles at the amount of content there. I’m starting as small as I can though because I know if I don’t, I’ll get bogged down in possibilities and spend my time wading through the 1% instead of just shipping the thing. recommends starting with Notes:

Simplest post building block. All posts have a simple text component, whether name, caption, comment, or text equivalent. Build notes first, and then you have a building block you can build upon for every other post type. This is the smallest step you can take to owning the timestamped content you publish.

That’s that then. Notes it is.

My notes will initially be a combination of thoughts and links with comment. The link posts needed a little bit of work to get WordPress mimicking the structure of Jeremy Keith’s Links post. The main issue was getting the post title to link to the source URL and not my post URL. This is straightforward WordPress custom field territory. However, I’m not keen on opening WordPress, creating a new post, copy-paste quote, copy-paste link into a custom field etc every time I want to share a link. Not keen at all.

Jeremy created a bookmarklet to do the heavy lifting for his workflow:

there’s also my own home-rolled bookmarklet for posting links to my site. It doesn’t do anything clever—it grabs the title and URL of the currently open page and pre-populates a form in a new window, leaving me to add a short description and some tags.

Inspired to recreate something similar with WordPress, I came to the Press This plugin; a replacement for the once-native functionality of WordPress. It creates a bookmarklet which mirrors most of the desired functionality — grabbing page title, URL and any selected content whilst also allowing you to tag and categorise the prior to publishing. This is all done via a pop-up window with a WordPress form. The only trouble is that the page URL is only available within the new post’s content and not in its own meta field, e.g.:

<blockquote>there’s also my own home-rolled bookmarklet for posting links to my site. It doesn’t do anything clever—it grabs the title and URL of the currently open page and pre-populates a form in a new window, leaving me to add a short description and some tags.</blockquote>
<p>Source: <em><a href="">Adactio: Journal—Links, tags, and feeds</a></em></p>

With the new post on my website, I want the post title to link to the URL from the Source from the snippet above. This involves a bit of regex (not my strong suit!), to extract the link from the content:


// get the post content
$content = wpautop(get_the_content());

// ready an empty array
$matches[] = '';

// populate an array with all links in the content 
preg_match_all('#\bhttps?://[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/))#', $content, $matches);

// display title with link to post
if ( has_category('links') ) {
    // use last link in content as destination
        the_title( '<h3><a href="' . end($matches[0]) . '" rel="bookmark">', '</a></h3>' );
} else {

One last thing — to remove the Source paragraph from the content so that the link isn’t duplicated. A dirty little CSS display: none would do it but feels hacky. I opted for this:


// get position of last <p> i.e. start of 'Source: ' block
$end = strrpos($content, '<p>');

// display content, stripped of Press This source i.e. last paragraph
echo $trimmed = substr($content, 0, $end);

This is just a toe in the ocean for being all IndieWeb, but it’s a start.