Extension(alism)

How to create a Textpattern plugin

Stef Dawson 6 September 2012

Love it or hate it, PHP is part of the core fabric of the web, driving blogs, services and corporate sites of all sizes. Textpattern is no exception. The CMS core code is deliberately kept as light and airy as possible, striving to keep out of the way of the process of creating and managing great content. Instead of piling features in, Textpattern prefers offering hooks so that other code can extend the core with PHP. If the thought of dirtying your hands in the squiggles and brackets of a programming language sends you running for the nearest shower, you might be surprised how easy it is. Thanks to Textpattern’s flexible tag system, site designers are already well versed in the concepts of conditional logic. The leap to writing a fragment of code to make you or your clients’ life easier is just a small step further.

A Textpattern plugin is nothing more than a container for some PHP and associated documentation. You can either write it in your favourite text editor using the standard plugin template to compile it into the fat .txt block ready to distribute to the world, or use the Plugin composer graphical interface. Since it’s simpler, we’ll use the composer here so grab a copy and install it from your Textpattern Admin > Author Edit panel.

For demonstration purposes, we’ll write a simple admin-side plugin to alter the author information on the Write panel. This appears below the excerpt in a standard Textpattern installation when you view a published article. Instead of showing the author’s login ID and published / modified dates, we’ll show the author’s real name, linking it to the Admin > Author Edit panel for editing, and a link to the author’s other articles.

Before diving headlong into code, it’ll be useful to know how Textpattern hooks work.

Plugin grappling hooks: callbacks

Every page you visit in your browser runs the PHP core code and plugins, and each page is split into blocks of content. On the Write panel there are many such blocks: the title, body, excerpt, article image, section, category list, status, and so forth. As the interface is drawn to the screen, Textpattern raises its little paw in the air at key points and asks “Any plugins want to modify this snippet of content before it hits the screen?” That’s a callback.

If the answer is yes, Textpattern runs the plugin code that requested interest; otherwise it continues as normal and draws the default UI element. In this case, we want to modify the bit of the screen showing the author information so we need to find the correct hook.

Consulting the core callback list and scrolling to the Write panel section, from Textpattern 4.5.0 there’s a hook that serves this purpose:

  • event: article_ui
  • step: author

Great! Time to don those coding trousers.

Attaching the plugin to a hook

Every plugin needs a prefix; three letters that uniquely identify you as the author. If you’re releasing plugins you can register your prefix, but for this exercise we’ll use xyz.

  1. Start the Plugin composer and put in xyz_author_info as the plugin name, then hit Create new plugin.
  2. Open up the Meta information twisty and set the Plugin type to Admin, as we’re creating an admin-side plugin.
  3. Make sure the Enable checkbox on the right is set. You can fill out any other info here if you like, but it’s not important right now.
  4. Open up the Plugin code twisty to reveal the textarea awaiting your code.

The first thing to do is register your plugin’s interest with Textpattern. This line of code does it:

register_callback('xyz_author_info', 'article_ui', 'author');

For our purposes, Textpattern’s register_callback function takes these parameters:

  1. Name of the plugin function to run.
  2. Textpattern ‘event’.
  3. Textpattern ‘step’.

When it comes time for Textpattern to draw the author part of the article UI, our plugin is told to run the function called xyz_author_info. We’d best write a container for it:

function xyz_author_info($evt, $stp, $markup, $row) {
}

You can go ahead and save the code after that: it won’t do anything yet, as it’s just a stub inside which we’ll put the remaining code. The name of the function matches the name stated as the first parameter to the register_callback function.

Exactly akin to the way the register callback function has parameters, Textpattern passes some info to our function:

  1. ‘event’: assigned to the $evt variable. In this case we know it will be ‘article_ui’.
  2. ‘step’: assigned to the $stp variable. We know this will be ‘author’.
  3. ‘default’: The default HTML markup that Textpattern intends to render if we don’t change it. We chose to assign this to the variable named $markup.
  4. ‘data’: assigned to the $row variable. This is simply a list of everything in the database that Textpattern knows about the currently edited article; its name, title, section, posted dates, etc. We can use this to make decisions over what to display.

Making the function do something

The row Textpattern sent us has almost everything we need. Firstly, to save typing, we’re going to extract everything from the row so we can access it directly in our plugin as a variable prefixed with a dollar. The PHP function extract does this for us:

extract($row);

After that line, instead of using array terminology like $row['AuthorID'] to get the contents of the article author’s login name, we can use $AuthorID directly, which is much shorter. Note that although this variable is named AuthorID, in this case the name is confusing because it holds the author’s login name!

While we’re saving ourselves some typing, let’s go ahead and use a built-in function called doSlash which makes the $AuthorID safe to use in a database query. As well as good security practice, if the $AuthorID was des o'connor the apostrophe would be ‘escaped’ after running it through doSlash, so it doesn’t break the query. Always use doSlash when passing anything to the database.

We’re going to assign the safe version of the name to a variable called safe_author, so we know that we can use it later in the code without having to sanitize it again:

$safe_author = doSlash($AuthorID);

Having a naming convention like this helps when programs get more complex, so it’s good to get in the habit.

In order to jump to the Admin > Author Edit screen we’re going to need the Author’s ID (number), which is not present in the row that Textpattern sent to our plugin. Thus we need to fetch it from the database using another built-in Textpattern function called fetch:

$author_ref = fetch('user_id', 'txp_users', 'name', $safe_author);

The fetch function needs to know the database column we want (user_id), which database table we want to get it from (txp_users), which column we want to match against (name) and what we want to match.

In terms of setup, we’d also like to know how many articles the author has written so we can link to the article list page as a handy shortcut. So we need to ask the ‘textpattern’ table how many articles are assigned to the author’s login name:

$num_articles = safe_count('textpattern', "AuthorID='$safe_author'");

Again, the structure of the statement is similar to those before it, this time using Textpattern’s safe_count function and assigning the resulting value to the variable $num_articles.

Writing the author info to the screen

At this stage we have set up all the variables we need so it’s time to prepare the HTML output. To keep things simple we’re going to piece it together part by part, adding each chunk to an array which we’ll join together right at the end into a long string for display. We could use PHP’s concatenation operator—the dot—to build it up as we go, but that makes for long, unwieldy lines of code that are not much help when learning.

$out[] = '<p class="author">';
$out[] = '<small>';

Those two lines just add the HTML paragraph and <small> tags to consecutive rows of the $out array. The empty square brackets mean “add it on the end of what’s already in the array”. If the array doesn’t exist, PHP creates it for you.

$out[] = gTxt('posted_by');

gTxt is Textpattern’s internationalisation function which returns the correct language string for the phrase assigned to the posted_by name. In the txp_lang table you’ll see hundreds of these so if you ever need to display anything, check to see if what you want is in there first. If not, the plugin composer allows you to add your own strings as part of a Textpack, but that’s outside the scope of this article.

Next, we want to display a hyperlink to the Admin > Author Edit panel so we can edit the user details. For the link, we’ll use another built-in function called eLink which takes these parameters:

  1. ‘event’ of the link destination
  2. ‘step’ of the link destination
  3. URL parameter name
  4. URL parameter value
  5. Link text
  6. Additional URL parameter name (optional)
  7. Additional URL parameter value (optional)

The following line is the one that produces our anchor tag for us. We’re not using the optional parameters this time—we’ll use those in a moment for the article count link. Note that the 5th parameter uses a built-in Textpattern function called get_author_name which does exactly what it says on the tin: gets the author’s Real Name from the $AuthorID that we already have.

$out[] = eLink('admin', 'author_edit', 'user_id', $author_ref, get_author_name($AuthorID));

After that we want to show the article’s posted date. This uses a format string familiar to users of Textpattern’s txp:posted tag, passed instead to the built-in function safe_strftime along with the $sPosted value that Textpattern gave us as part of the $row earlier:

$out[] = ' &#183; ';
$out[] = safe_strftime('%d %b %Y &#183; %X', $sPosted);
$out[] = ' &#183; ';

We also surrounded the date with HTML ‘middot’ characters to make it look prettier.

The last part of this segment involves adding a link to the article list panel, showing the articles owned by the current author:

$out[] = eLink('list', 'list', 'search_method', 'author', $num_articles . sp . gTxt('articles'), 'crit', $safe_author);

Nothing really new here, apart from the fact the link text itself is made up by concatenating num_articles, a non-breaking space (shortcut: sp) and the internationalised word ‘articles’.

Next we want to show the modified date, but only if the article has been modified. So we need to test this using an if construct with the “not equal” operator:

if($sPosted != $sLastMod)

In other words, if the posted date is not the same as the last modified date, then…

$out[] = br;
$out[] = gTxt('modified_by');
$out[] = txpspecialchars($LastModID);
$out[] = ' &#183; ';
$out[] = safe_strftime('%d %b %Y &#183; %X', $sLastMod);

Most of this is now familiar ground. br is Textpattern’s shortcut for the HTML <br /> tag, and txpspecialchars is a built-in function for making sure that the displayed info inside the parentheses is ‘clean’, i.e. not going to make the page validator complain. We use this on any strings we’re not sure about (for security purposes), or those that may contain encoded character sequences.

Almost done. Time to finish off the HTML by adding the closing markup:

$out[] = '</small>';
$out[] = '</p>';

Finally, join all the elements of our array together with Textpattern’s shortcut for ‘newline’— the single letter n—then return the entire HTML block to Textpattern for displaying on the screen in place of the default markup:

return join(n, $out);

And that’s it. Save your masterpiece and view an article you’ve already published in the Write panel. Tada!

For reference, the plugin in all its glory is shown in Figure 1. Not too scary after all.

register_callback('xyz_author_info', 'article_ui', 'author');

function xyz_author_info($evt, $stp, $markup, $row) {
   extract($row);

   $safe_author = doSlash($AuthorID);
   $author_ref = fetch('user_id', 'txp_users', 'name', $safe_author);
   $num_articles = safe_count('textpattern', "AuthorID='$safe_author'");

   $out[] = '<p class="author">';
   $out[] = '<small>';
   $out[] = gTxt('posted_by');
   $out[] = eLink('admin', 'author_edit', 'user_id', $author_ref, get_author_name($AuthorID));
   $out[] = ' &#183; ';
   $out[] = safe_strftime('%d %b %Y &#183; %X', $sPosted);
   $out[] = ' &#183; ';
   $out[] = eLink('list', 'list', 'search_method', 'author', $num_articles.sp.gTxt('articles'), 'crit', $safe_author);

   if($sPosted != $sLastMod) {
       $out[] = br;
       $out[] = gTxt('modified_by');
       $out[] = txpspecialchars($LastModID);
       $out[] = ' &#183; ';
       $out[] = safe_strftime('%d %b %Y &#183; %X', $sLastMod);
   }

   $out[] = '</small>';
   $out[] = '</p>';

   return join(n, $out);
}
Figure 1: The final xyz_author_info plugin.

Callbacks galore

There are loads of callback points to play with, and additional information on how to interact with Textpattern at the programmer level can be found in the documentation. People in our forum or social channels will gladly offer advice on the core functions available, security, and best practice so you can flex your coding chops.

As a parting gift, Figure 2 shows a tiny admin-side plugin for you to mull over that uses another callback introduced in Textpattern 4.5.0. Plugin xyz_for_your_eyes_only filters all the content types by author so anyone other than Publisher accounts can only see their own articles, images, files, and links:

register_callback('xyz_for_your_eyes_only', 'admin_criteria');

function xyz_for_your_eyes_only($evt, $stp, $crit) {
   global $txp_user;

   $safe_user = doSlash($txp_user);
   $privs = safe_field('privs', 'txp_users', "name='$safe_user'");

   // Publishers (privs '1') get to see all articles; everyone else sees a filtered list
   if ($privs !== '1') {
       if ($stp == 'list_list') {
           return " AND AuthorID = '$safe_user'";
       } else if (in_array($stp, array('link_list', 'file_list', 'image_list'))) {
           return " AND author = '$safe_user'";
       }
   }
}
Figure 2: The xyz_for_your_eyes_only plugin: only show non-publisher authors their own content

Before you know it, you’ll be twisting Textpattern’s already nimble frame to do your bidding.

Happy coding!

comments powered by Disqus