Custom developments behind TXP Magazine

Stef Dawson 31 May 2012

To bring this magazine to fruition required gently twisting Textpattern from managing linear date-based or date-independent content into clusters of easily navigable, issue-based articles. Although fairly straightforward, it’s not as simple as it sounds, and took a few waves of the PHP wand to accomplish—some TXP Mag(ic), if you will. Several plugins stirred into the cauldron also made the workflow simpler from the inside for the editorial team. So put on your pointy black hats and find out how the spells were cast to turn the magazine you’re reading from an outline drawing of a frog into a curvy princess.

When I first floated the idea of reviving the magazine I thought it’d be a simple case of refreshing the layout, writing some great content, and adding the articles to Textpattern. Then Destry drew up how it should work, the rest of the team hashed out the ideas, and I then took the reins to turn the conceptual model into a functional reality.

Determining the site layout and structure

After analysing the proposed layout, taxonomy, and editorial chart, it became clear we’d have a lot of Textpattern sections, just like the previous magazine version. While the old site had many page templates to complement its wide section structure, I wanted to reduce this number to five (excluding error pages) for future maintainability. Each page is virtually identical, holding just the skeleton HTML 5 markup to dictate the layout: head, body with container, title, navigation links, main and complementary areas, and a footer. Conditional tags were kept to a minimum, so the hard work was up to Textpattern’s workhorse building block—{forms}.

A naming convention was adopted to group forms in the admin-side panel. For example, head- was the prefix used for anything that occurred in the header, page- for anything in the main article/sidebar flow, and foot- for footer content. Hyphens were used to separate logical areas:

  • page-main : regular article markup
  • page-comp : sidebar content
  • page-main-people : a special version of the main article area for the ‘people’ section

Form names were picked to suggest their function and each form is documented—both inline and in a separate file—for future reference. The smd_where_used and adi_form_links plugins help navigate the admin side efficiently.

URL-based customisation

Site designers often use Textpattern’s custom fields to collect article-centric metadata, and use the glz_custom_fields plugin to obtain more than the stock number of ten.

In the magazine, we use a grand total of one custom field. It’s called Issue. The cleverness rests in how we use it and how it interplays with the URL to give visitors a context. Some examples:

Visitor action Context
Viewing a front page or a column landing page Current issue
/issues/2 Issue 2
/issues/2012 Highest Issue from 2012
/issues/2012/02 Issue from February 2012
Reading an article in issue 3 Issue 3

Everything visitors do on the site revolves around this context, and it’s determined through a few simple gbp_permanent_links rules to prevent /issues/N delivering a 404, a few lines of PHP and a set of variables. Note that the date-based URLs with 2012 in them effectively limit the number of potential issues. It has been arbitrarily set at an upper value of 2001, which is about 500 years’ worth of quarterly publications, and was deemed an acceptable risk. Even if we were to publish one issue per month it gives us 165 years to come up with a different URL scheme, by which time the Internet will have either disintegrated or will be part of your clothing.

The context is set at the top of every page; after that, the site configures itself to the relevant context. Figure 1 shows an excerpt from our head-context form, which runs immediately after the Doctype and meta information has been set:

<!-- Set up some lists of sections -->
<txp:variable name="column_sections">
from-the-editor, community-spotlight, extensionalism, independents-frontline, hope-for-the-future, meaningful-labour
<txp:variable name="ad_sections">
, columns, issues, topics, exhibit, <txp:variable name="column_sections" />

<!-- Testable conditional boolean variables -->
<txp:variable name="is_column">
<txp:if_section name='<txp:variable name="column_sections" />'>
<txp:else />

<txp:variable name="has_ads">
<txp:if_section name='<txp:variable name="ad_sections" />'>
    <txp:if_status status="404">
      <txp:if_section name="">
      <txp:else />
    <txp:else />
<txp:else />
Figure 1: Setting Textpattern context. The code here has newlines and spaces added for readability.

The general idea is to set up txp:variables containing lists of values and then set up a second set of variables that contain a zero or one value, based on whether the current section, page, or article matches some condition against each list. These can be tested very easily with if_variable anywhere later in the page to decide whether to include or exclude some content.

Using PHP to bridge functionality

Because Textpattern is a blogging engine (show the most recent items first) and a CMS (show the given content) hybrid, it can’t natively tie the concept of an issue number together with a publication date and the URL.

The great thing about Textpattern is its utter flexibility and the fact that embedding a few lines of PHP to fill any gaps is only a tag away.

There are three steps to determining an issue number. While the code itself is terse and would be tough to follow if reprinted here, the process behind it is as follows:

Step 1: URL-based issue

  1. Is the document status 200 and the Section named “issues”?
  2. If so, grab the REQUEST_URI and remove any trailing ? part.
  3. Split what remains at the forward slash character.
  4. Find the word ‘issues’.
  5. If the next item is numeric and not a year, consider it an issue number.
  6. If it’s a year, look at the subsequent parts and inform Textpattern of the supplied date.

Step 2: article-based issue

  1. If the issue has not yet been set, check if the current article contains an issue number in the custom field.
  2. If so, set the issue number to the article’s issue.

Step 3: latest issue

  1. If the issue has not yet been set, ask the database for the highest issue number in a custom field from a Live, already published article in one of the defined column_sections, taking any supplied date into account.
  2. Set the issue number to this value.

Whenever an issue number is found, the corresponding month and year of the publication is also retrieved so the correct date can be shown alongside it.

That’s all there is to it. In this case, as long as the Issue custom field is correctly filled out for each article, the CMS knows exactly what visitors are looking for, how they got there, and what to display.

Indexing magazine content

After the context and issue number are determined, the next piece of the puzzle is how to generate the table of contents for an issue. Initially, it might seem that tags-in-tags could come to the rescue:

<txp:article_custom issue='<txp:variable name="issue" />' />

Sadly, that won’t work. Magazine articles need to be in a specific order when appearing in lists; From the Editor is always first, for example. While we could manually change an article’s date or alter the section names (leaving dirty URLs), the extra overhead defeats the point of a content management system.

This little conundrum called for an smd_macro to emulate the important bits of <txp:article_custom>. The resulting code was packaged into this custom macro tag: <txp:in_issue>

The new macro tag can be used anywhere in the site where a given issue’s articles need to be listed in the correct order.

Maintaining article order

The sort attribute for Textpattern’s article_custom tag assumes a simple list of database column names plus asc or desc to indicate sort direction. Employing MySQL Field ordering and plugging such a list into the sort attribute using the tag-in-tag syntax doesn’t work because Textpattern processes the attribute (for security) before trying to use it, resulting in some ugly single- or double-quote disasters that break the query. While backtick characters are a possibility, it’s far from pretty.

With PHP at our disposal the restrictions can be easily lifted when building the <txp:in_issue> macro because the sort order is pre-computed and available from our head-context form.

The new macro tag (available for download at the end of this article for anybody interested in the nuts and bolts) has eleven attributes, including article_custom favourites such as wraptag, class, time, month, status and so forth. The tag performs an article query that orders the results by:

  1. descending issue number
  2. the list of sections, in the given order
  3. descending date

Functionally, the macro iterates over the resulting list of returned articles and processes them via the nominated form or the macro’s container. But there’s a catch: if the URL contains a date, or the month attribute is used, the list of returned articles may span more than one issue. This makes it rather difficult to style the lists because it becomes necessary to know when one issue ends and the next begins.

Solving this with plugins or txp:variables was possible, but messy. Instead, the macro included if_first_article and if_last_article to trigger when issue numbers changed, making it easy to employ conditional logic, while if_different was used to style multiple lists with one container tag. This is demonstrated in the landing page example in Figure 2:

<txp:in_issue issue="">
     <h2><txp:output_form form="article-issue" /></h2>
 <txp:if_article_section name="from-the-editor">
     <p><txp:excerpt /></p>
 <li><txp:permlink><txp:title /></txp:permlink>, <span class="label">by</span> <txp:output_form form="article-author" /></li>
Figure 2: Using the in_issue macro on a landing page.

The situation was a little more manual for the table of contents list on the homepage because we wanted to add a custom “teaser” description under each title. Using a custom field to hold the teaser was an option, and we could then have used <txp:in_issue /> again to list the contents. But since the Editor in Chief writes this copy and decides the content order just prior to publication, we didn’t want to burden that person with having to visit each article in turn, edit a custom text field and save, then potentially tinker with the article order elsewhere.

To consolidate the functionality into a single place, the task fell to smd_featured. The description field provided by this plugin is perfect for writing about each article, and the order is easily controlled via the position widgets. The articles are grouped by a label called issue_N with N being the issue in which the articles appear. This allows simple, complete, focused and, most importantly, automatically updated display of an issue’s table of contents from a single <txp:smd_featured> tag in the homepage template.

Simplifying image insertion

To tune up inline image handling, smd_macro was brought in again. The custom <txp::figcap> tag (attached below) allows us to easily insert images that have been uploaded to Textpattern. The caption for the image can either be read from the image itself, supplied via the tag’s caption attribute, or written in its container.

Further, a (currently unfinished) plugin based on jmd_img_selector will allow us to click a button on the admin panel to choose one or more images via thumbnails and have that plugin insert the macro automatically for us with the selected attributes and positioning class. This streamlines the editorial workflow to this:

  1. Upload image.
  2. Place caret marker where image is to go.
  3. Click Insert Image.
  4. Select image(s) and decide whether they are to go full width, left, right, et cetera.
  5. Click Do It.

The plugins take care of the rest for us and, in fact, I have plans to simplify this even further before the plugin is made public.

The major advantage of a macro is that if we decide to alter the layout, we only need to change the macro once. Any time an article is visited, the new markup is delivered automatically which means we don’t have to open every article in the admin interface, alter its markup and resave.

Supplemental content

As a last incantation, we automated the rotational display of quotations. They are just regular Textpattern links, pulled into the article randomly using another smd_macro, this one wrapped up as <txp::quotation>. The goal here was zero setup: we wanted the ability to randomly insert quotes without duplication elsewhere in a given page view.

The macro yet again utilises the power of <txp:variable>. The first time the tag is used, if the variable called ‘quotations’ has yet to be created, it creates it by forging a linklist and grabbing up to 10 quotes, which is more than enough for display on a page. Each quote is delimited inside the variable with a special character sequence via the break="@#~#@" attribute.

Once the variable has been created, the rest of the tag just increments a PHP counter so that each time the tag is called, the next number in the sequence is retrieved. The quotations variable is then split at the special character sequence (using smd_wrap) and the Nth item returned, where N is the sequence number.

Magazine publishing made easy

Turning Textpattern from a lean, mean publishing machine into a lean, mean, issue-based publishing machine is no more complicated than setting up some variables to control what to display later in the page. The gbp_permanent_links rules could be created in .htaccess rewrite rules, while the macros merely serve as a handy way to bring everything together: the functionality could equally be achieved, with slightly less flexibility, through the output_form and yield core tags instead.

From Textpattern’s power, the wide range of plugins available, and the simple manner in which functionality can be expanded using PHP, we have everything needed to conjure up almost any site you can envisage. A spellbinding site—whether yours or a client’s— is solely down to combining the elements in creative ways.

Figure+caption macro

In-issue macro

Figure 3: macros for download.
comments powered by Disqus