PHP Dynamic Publishing: Developing Plugins
Note: This tutorial was first published on October 29th for members of the Professional Network. If you'd like to get access to the benefits of membership in the network, sign up now.
Our last tutorial covering dynamic publishing provided a high-level technical overview of its implementation. In this tutorial, we will examine the way you create plugins for the PHP dynamic publishing model. It varies a bit from the Perl model, since we have built the template processing on top of Smarty, a popular and powerful templating engine for PHP.
Use the Source, Luke!
To get you started, there are a whole host of MT tags that have been ported from Perl to PHP. These would be the core MT tag set. The Perl and PHP source is available for both with the 3.1 (and later) distribution, so that should give you a good feel for how to do a number of different types of tags. If you're comfortable with how the Perl API works, then you can compare the Perl and PHP implementations for standard MT tags that are similar to your own and see how the Perl code was translated to work for PHP.
In creating the PHP implementation, we tried to preserve some concepts from the MT/Perl API. However, some things are drastically different and/or simplified, partly due to the Smarty integration and partly due to the nature of dynamic publishing: execution speed overrides everything else.
One thing that will be immediately noticable is that the PHP API has a much simpler object model. In fact, there are just a handful of classes compared to the Perl implementation. For example, instead of having classes defined for each table, we simply use a PHP array to hold data.
Speed, Speed, Speed
Speed is critical to dynamic publishing, so it is very important that your plugin execute efficiently. Every tag used on a dynamic template will take some time to execute. If your plugin uses even 1 second of time, that will be very noticable to someone browsing that page. And if 20 people are viewing it at once, then chances are it will take even longer per person for that page to render. This is the cost of dynamic publishing.
Creating Custom Tags
We'll start by looking at what you need to do to create a plain custom tag. In Smarty-speak, these are called function tags.
- Create a PHP script, named "function.MTMyTag.php"
- Within that script, define a PHP function, named "smarty_function_MTMyTag"
That's it! The file naming convention is a requirement of Smarty. This is done so that Smarty can locate and load the appropriate source code at runtime to invoke the tags necessary for a given page. Unlike with MT, where each individual plugin file is processed and loaded, Smarty only loads the PHP scripts that will be necessary to execute a given template. In the end, this winds up being a more efficient approach and a good technique to follow for the dynamic publishing model.
The obligatory "Hello, World" tag
Let's say you want to create a template tag that offers a greeting. Let's call it MTHello. The first step would be to create a PHP script named "function.MTHello.php". This script would be stored in the MT_DIR/php/plugins directory (substituting your MT installation path for MT_DIR of course).
Within the file, you would define the following function:
- <?php
- function smarty_function_MTHello($args, &$ctx) {
- return "Hello, World!";
- }
- ?>
This is the simplest of all tags. It simply returns a string. We can do better than that!
- <?php
- function smarty_function_MTHello($args, &$ctx) {
- $name = $args['name'] or 'World';
- return "Hello, $name!";
- }
- ?>
Now we're cooking. We can now accept a name attribute with the MTHello tag. It would be used like this:
With name attribute: <$MTHello name="Visitor"$>
Without name attribute: <$MTHello$>
Producing:
With name attribute: Hello, Visitor!
Without name attribute: Hello, World!
Ah, but we can do even better. If the user has signed in using TypeKey, then we should be able to personally greet them. And yes, we can:
- <?php
- function smarty_function_MTHello($args, &$ctx) {
- $name = $_COOKIE['commenter_name'] or $args['name'] or 'World';
- return "Hello, $name!";
- }
- ?>
So now, if I am logged in, the above template code would produce:
Hello, Brad Choate!
Creating Container Tags
Let's look at what you do to create a custom container tag. We'll also create a related custom tag that is to be used within the container tag. Container tags are called "block" tags in the Smarty vocabulary.
The container tags work quite differently in PHP as compared to the Perl approach. This is due to the nature of how the block functions are called from the compiled templates. Some pseudocode is in order to illustrate the process. Consider this template:
<MTMyContainerTag>
I'm content being contained.
</MTMyContainerTag>
That template would be compiled into something like this (The following is similar to what Smarty would produce when compiling the above template. This is not code that you or a user has to create by any means. I've just added comments and spacing to make it more readable.):
- <?php
- # push the active tag onto the tag stack
- # (and any attributes it uses)...
- $this->_tag_stack[] = array('MTMyContainerTag', array());
- # call the block function once to do the initialization
- # step. it could set the $repeat parameter to false to
- # prevent *any* output.
- smarty_block_MTMyContainerTag(
- $this->_tag_stack[count($this->_tag_stack)-1][1],
- null,
- $this,
- $_block_repeat = true);
- # start a loop that ends once the $repeat parameter is
- # set to false (which it is by default-- the container
- # tag handler must explicitly set it to true to continue
- # looping)
- while ($_block_repeat) {
- # start output buffering since mixed php/plain text
- # usually follows...
- ob_start();
- # the stuff within the container tag comes next:
- ?>
- I'm content being contained.
- <?php
- # now grab the buffered content and store it so
- # we can pass it back to the container tag handler:
- $this->_block_content = ob_get_contents();
- ob_end_clean();
- # echo out whatever the container tag returns as
- # the result...
- echo smarty_block_MTMyContainerTag(
- $this->_tag_stack[count($this->_tag_stack)-1][1],
- $this->_block_content,
- $_block_repeat = false);
- }
- # our loop is complete; remove the tag from the stack
- array_pop($this->_tag_stack);
- ?>
This should help you see what is taking place when the actual block function is being called and how the block function can control the while loop it lives within. The form of the actual block handler looks like this:
- <?php
- function smarty_block_MTMyContainerTag($args, $content, &$ctx, &$repeat) {
- $localvars = array('one', 'two', 'three');
- if (!isset($content)) {
- # initialization and setup...
- $ctx->localize($localvars);
- } else {
- # code that executes iteratively while $repeat == true
- # optionally set $repeat to true to loop
- # manipulate the data in $content if so desired
- }
- if (!$repeat)
- $ctx->restore($localvars);
- return $content;
- }
- ?>
Compare the compiled template shown previously with the code for the block handler and you will start to see the relationship between them. In the compiled template on line 8, the block handler is called and winds up executing the section of code on line 5 ("initialization and setup"). Since this happens outside the loop, it's a good place to do any database fetch operations that determine whether or not the loop should execute. You can take advantage of the $ctx->stash() method to store any data you want to maintain or to make available for any contained custom tags.
Preserving stash elements
Since PHP doesn't have a local statement like Perl does, a pair of routines have been created to provide the same function. The $ctx->localize() method receives an array of named stash elements whose values are to be saved for later restoration using the $ctx->restore() method. It is important to pair these properly whenever they are used.
Traditional Container Tags
Now that you understand how container tags operate and are coded, let's look at a real example.
- <?php
- function smarty_block_MTMySuckyTag($args, $content, &$ctx, &$repeat) {
- $localvars = array('myvar');
- if (!isset($content)) {
- $ctx->localize($localvars);
- $ctx->stash('myvar', $args['string']);
- } else {
- $myvar = $ctx->stash('myvar');
- if (strlen($myvar) > 0) {
- $repeat = true;
- $myvar = substr($myvar, 0, strlen($some_var)-1);
- $ctx->stash('myvar', $myvar);
- }
- }
- if (!$repeat)
- $ctx->restore($localvars);
- return $content;
- }
- ?>
Then, to compliment this wonder block tag, a tag to output the value of 'myvar':
- <?php
- function smarty_function_MTMySuckyVar($args, &$ctx) {
- return $ctx->stash('myvar');
- }
- ?>
Then, using these tags like this:
<MTMySuckyTag string="Testing">
<$MTMySuckyVar$>
</MTMySuckyTag>
Which would produce...
Testing
Testin
Testi
Test
Tes
Te
T
Brilliant.
Conditional Container Tags
Conditional tags operate a little differently and we'll look at that next. The <MTElse> tag is the issue here. We have to have a way to process that tag effectively.
Then, you can use the conditional tag like this:
<MTIfRandom>
You got this message by chance.
</MTIfRandom>
Or you can use the <MTElse> tag to compliment it:
<MTIfRandom>
You're a winner!
<MTElse>
You're a loser!
</MTElse>
</MTIfRandom>
Global Filter Plugins
Global filters are called "modifiers" in Smarty. They are very easy to create. Their filename prefix is "modifier", so modifier plugin file names would look like "modifier.my_modifier.php".
- <?php
- function smarty_modifier_my_modifier($text, $attr = null) {
- # do something with $text
- $text = strtoupper($text);
- return $text;
- }
- ?>
If you have some third-party module you want to use, just use the include_once PHP statement to include the library and then call the text processing routine in the modifier function, returning the result.
Initialization Plugins
Another kind of plugin supported (starting with MT 3.12) are initialization plugins. These have an "init" prefix for plugin scripts. With MT/Perl, all ".pl" plugin files in the plugins directory are loaded at startup. Initialization scripts are the counterpart to that mechanism. To take advantage of this, you will need to name your script so that it has an "init." prefix and a ".php" suffix. Any files in the MT_DIR/php/plugins directory will be processed by the $ctx->init_plugins() method before the request is processed. The script is simply loaded with a PHP include statement.
Instructing the template compiler
The process that translates MT templates into Smarty templates requires some hints about particular tags and attributes in order to do that translation properly. In particular, conditional-style tags (any tag that is used in combination with the <MTElse> tag) are treated differently. Tags that have "If" in their name are recognized as such, but those that do not (such as <MTEntriesHeader>) must be declared as a conditional tag. Also, global filters (Smarty "modifiers") have to be declared so they are not processed as parameters to the tag handlers (however, these will be recognized automatically since they can be discovered by scanning for files that have a "modifier." prefix in the plugins directory).
Lastly, if you have a tag that recursively calls itself (the <$MTSubCatsRecurse$> tag fits this category), then that has to be known as well. If your plugin has a tag or tags that need to be declared, you should write an "init.myplugin.php" script to do that. For example:
- <?php
- global $mt;
- # This retrieves the Smarty instance we use to
- # declare our template compilation hints...
- $ctx =& $mt->context();
- # Declares a conditional tag that doesn't have "If"
- # already in the tag name.
- $ctx->add_conditional_tag('MTMyConditionalTag');
- # Declares a tag that requires a "token" function.
- $ctx->add_token_tag('MTMyTokenTag');
- ?>
Conclusion
That covers the plugin architecture for creating custom template tags. The next tutorial will cover the database layer.



8 Comments
thanks for feedback guys Greetings
Nice atricle - best regards.
mayby im stupid, but in 99% case i have problem with install plugins...
PHP Dynamic Publishing? Now AJAX (Asynchronous JavaScript and XML) is on the top.
Ok, so when wants to program a web page to greet an individual, one follows all of this code. As with all of your tutorials, this one is excellent, and clearly explains how to make this happen. In working on my own, admittedly amateur page, I will definitely try to apply your directions. What really boggles my mind, though is the fact that there are people somewhere in the world working on how to make a computer think for itself rather than just respond with a kind of prescribed greeting. And, as a beginning programmer, I have to say, I’m terrified of the possibility that, as soon as I get my page up and running good, and feel as though I’ve mastered something of what you’re teaching here, that suddenly programming will be about something infinitely more complicated. Is the day already fast approaching when amateurs simply won’t be able to do this anymore?
My blog runs fine before but the last time I changed the setting to dynamic something went wrong. After rebuilding the site, all recent posts appear to show up correctly in the main page. But if anyone goes browsing in the archives for any previous entries, they'll be greeted with a file not found error.
Great piece of code, on my Blog it worked perfectly
PHP Dynamic Publishing? Now AJAX (Asynchronous JavaScript and XML) is on the top - thats true, but it is not the only way...