View Full Version : [How-To] Using Plugins for Automatic Template Edits

03 Jul 2007, 09:29
Using Plugins for Automatic Template Edits

Would you like to do make template edits without having to edit a template? Ever wondered how other mods can seem to make changes without you having to edit a single template?

The answer is vBulletin's plugin system. Before we go on you should be familiar with how to edit a template, how templates work and the basics of the the plugin system. There are other tutorials that explain these concepts, if you are new to this, please take some time to learn this stuff first - it will make it much easier to understand the concepts I'm about to present. You can find other tutorials here:

How to include a custom template via plugins (http://www.vbulletin.org/forum/showthread.php?t=119933) by Kerry-Anne
Template editing (http://www.vbulletin.org/forum/showthread.php?t=113489) - by Toolblast
Template mod FAQ (http://www.vbulletin.org/forum/showthread.php?t=85526) - by Sephiroth

You should also browse other tutorials in the articles section if you need to fill gaps in your knowledge.

You'll also need a very basic understanding of PHP (http://devzone.zend.com/node/view/id/625), but if you are new to this - don't worry you can pick it up as you go along. I've made this tutorial much easier than most others. So if you are beginner, or never tried making your own mod, please try to follow.

Right, let's begin.

How does the template thing work?

When your page loads from the server, PHP runs through the file, changing variables (http://en.wikipedia.org/wiki/Variable) and getting certain data ready before presenting it to your web browser. Take a look through one of those files. Let's take a look inside online.php. Do a text search for "fetch_template" and see how many times you can see this appear in the file. You'll notice that this file is loading several templates. Take a look right at the end of the file and you'll see the last template is loaded like this:
eval('print_output("' . fetch_template('WHOSONLINE') . '");');Our template name is WHOSONLINE, and we could edit this manually in the style manager if we wanted to - but that's old school - we are going to do it with the plugin system. Take a look a few lines above and you'll see that a hook is available just before this template loads:
($hook = vBulletinHook::fetch_hook('online_complete')) ? eval($hook) : false;Now, the important point to understand here is that each template is really a big block of text. This big block of text is stored in a variable. We refer to this as a string (http://en.wikipedia.org/wiki/String_%28computer_science%29). Now since we've realised that our templates are really just giant strings, we can use string manipulation functions (http://en.wikipedia.org/wiki/Function_%28computer_science%29) to change them before they are presented to the user.

Using code executed at hooks to dynamically change the template

When we change templates with the plugin system there are many hooks we can use to do this - we just need to use a hook that is called somewhere before our template is processed. Choosing the right hook depends on what you are trying to do or what the rest of your mod is doing, but generally try to use a hook as close as possible to the point where your template loads. For example, if we want to change something in WHOSONLINE, what would the point be of using a hook like global_start? That would change our template every single time a page (every page, not necessarily the who's online page) is loaded on your forum. It would be more efficient to only run the code that changes the template when we are running a page that actually needs to load the template. For this reason we will be using a hook in online.php, namely online_complete.

For our example, first let's decide what we want to change in the template WHOSONLINE. Let's load the template and think of something that we want to change in that template.

Adding to the template - finding our replacement point

We are going to make a fairly lame hack here. We are going to display the username of the currently logged on user above the who's online table. So we need to insert our new code above that table, so let's find where this table starts.
<table class="tborder" cellpadding="$stylevar[cellpadding]" cellspacing="$stylevar[cellspacing]" border="0" width="100%" align="center" id="woltable">How will we change the template dynamically?

Now remember that our template is a giant string, and we want to insert our changes into this string. How will we do this? The answer is a common php function called str_replace() (http://www.php.net/str_replace). This function searches for something and replaces it with something else. All we need to do is find something within our template which is unique and easy to search for. We then replace it with our new stuff and put the old stuff back in for good measure - this means we have effectively inserted something. It's a bit like if you had a word processor document containing the sentence "The quick fox". If we wanted to insert "brown" between quick and fox we could search for "fox" and replace it with "brown fox". Our text would then read "The quick brown fox".

Inserting our sample template changes

We apply exactly the same principle to our template. The code we want to insert in this example looks like this:
$bbuserinfo[username]But wait a second - if we actually tried to insert this code, it wouldn't work. Why not? Because this form of variable is like a shortcut when you are writing a template. However, inside the template itself, it is stored differently. Until we learn how to discover how they are really stored we are going to use this technique instead:
$currentuser = $vbulletin->userinfo['username'];
$replace = '$currentuser';Basically we are loading up our own variable with the data inside our plugin, then we are going to call our variable directly from the template. Note that we have used single quotes in the second line because we want the variable name, rather than the contents of the variable to be passed to the template. When the template is executed, it will then fetch our variable for us. If you need to understand single and double quoting a little better, try this tutorial (http://www.jeroenmulder.com/weblog/2005/04/php_single_and_double_quotes.php).

I recommend you quickly review this tutorial (http://www.vbulletin.org/forum/showthread.php?t=98047) by Psionic Vision about variables in vBulletin. As you read it, bear in mind that you can't use the variables in this form inside the template itself, but you can use them from inside your plugin. We'll learn more about why this is in other lessons, but for now, use the technique I've described above.

Creating our plugin

We'll make this code a little a larger than we normally would, in order to make it easier to understand. Let's create our new plugin at the online_complete hook. We'll built it line by line, with explanations along the way. If you don't know what I'm talking about at this stage go into AdminCP -> Plugins & Products -> Plugin Manager -> (scroll to the bottom) Add New Plugin.
$find = '<table class=\"tborder\" cellpadding=\"$stylevar[cellpadding]\" cellspacing=\"$stylevar[cellspacing]\" border=\"0\" width=\"100%\" align=\"center\" id=\"woltable\">';This is what we are going to look for, we've stored it in a variable called $find to make it easier to handle. Did you notice something? We've changed things slightly. Every time we have a double quote we need to "escape (http://en.wikipedia.org/wiki/Escape_character)" it. We do this by adding a backslash character to tell PHP that we really do want a double quote stored in the variable, rather than having PHP think that we are ending or beginning a string using the double quote character. We've also enclosed our string in single quotes. Now you might be saying that at this point "But single quotes mean I don't have to escape double quotes". This is true, but not in this case. If we actually looked inside the template, as it is stored in memory, we'd find those backslash characters. If we want our search to match, we have to put them in. We could have also used the addslashes() (http://www.php.net/addslashes) function to do this, but we are keeping this example simple, so we put them in by hand. Remember what you see when editing the template via the style manager is not always the same as what is actually stored in memory!

Let's get to our next line:
$currentuser = $vbulletin->userinfo['username'];
$replace = '$currentuser';We didn't have any special characters here, so we didn't need to escape anything (in the second line), but we enclosed the variable in single quotes anyway (see explanation above in the section where this code was introduced for the explanation about single quotes).

Getting down to business - str_replace()

Now we've defined what we are looking for and what we want to insert. Let's get down to business:
$vbulletin->templatecache['WHOSONLINE'] = str_replace($find, $replace . $find, $vbulletin->templatecache['WHOSONLINE']);Our template here is $vbulletin->templatecache['WHOSONLINE']. This command is basically saying that our template is equal to the result of the str_replace() function. Our function is going to find the occurrence of $find, and replace it with "$replace . $find" (see that? If we want to insert we have to put our original text back in!). Remember that the dot operator (http://www.php.net/language.operators.string) we just used is the same as a plus for text strings - it adds them together. The final parameter used in str_replace() is the string we want to work on - which is the template itself. Our function accepted three parameters, each separated by a comma. Go back and take a look between the round brackets if you can't identify those three parameters.

That's basically it - here's the code for our plugin:
$find = '<table class=\"tborder\" cellpadding=\"$stylevar[cellpadding]\" cellspacing=\"$stylevar[cellspacing]\" border=\"0\" width=\"100%\" align=\"center\" id=\"woltable\">';
$currentuser = $vbulletin->userinfo['username'];
$replace = '$currentuser';
$vbulletin->templatecache['WHOSONLINE'] = str_replace($find, $replace . $find, $vbulletin->templatecache['WHOSONLINE']);Try it - create a plugin at the online_complete hook and enter this code. Don't forget to enable your plugin - it is disabled by default. Load your who's online page and you'll see the change. Don't forget to disable to plugin afterwards, you don't want this change visible on your forum permanently.

Things to watch out for and other limitations

This has been a simple example. There are some more complexities, the biggest of which is that the conditional (http://en.wikipedia.org/wiki/Conditional_statement) statements in templates can't be easily replaced with this method and that any "special" variables won't work. They can be made to work, but how to do so will be the subject of a future tutorial.

Now that you know the technique, you need to bear the following in mind:
Consider the correct hook to use
Study an example from another mod, see how they did it
Choose simple text to search for within your template
Make sure what you search for is unique!
Make a manual template edit first, make sure what you want to do works. Then record the changes somewhere else (copy and paste into a text file), and revert the template to its previous state before starting your plugin.
Until you know how, avoid any conditional tags (<if>, <else> or </if>) in the text you will search for or replace.
Avoid searching for text in your template which runs onto more than one line. This means that there will be new line characters stored within the string, making life much more difficult. For now, just stick to inserting things onto the same line as something else - also watch out for tabs.
Many people manually edit their templates. Try to pick a simple chunk of text within the template that may not be a popular location to edit. Also consider providing manual template instructions in case your mod doesn't work for someone with a modified template.How to avoid conditionals with automatic edits

Not using conditionals inside the template might be a bit limiting for you,
but consider the following point. If you want something to appear based on a
certain condition, why not change it around a little bit and don't make your
template replacement unless that condition is met inside your plugin? This
will effectively give you the same functionality, but testing for the
conditional in your plugin is simpler.

Here's an example: Let's say you were going to do something like this with
your template (insert the first part in front of the second, existing part):
<if condition="$foo">Here is our mod!</if><div>Existing template data</div>Remove the conditional from the template and it put it into your plugin instead:
If ($foo)
$vbulletin->templatecache['yourtemplate'] = str_replace("<div>Existing template data</div>", "Here is our mod!<div>Existing template data</div>", $vbulletin->templatecache['yourtemplate']);
}This means if the condition wasn't satisfied we don't insert the text into our template. If it is, we add our text. This means we can avoid using the conditional in the template entirely. Of course, using conditionals in a template isn't a sin, it's just very difficult to do with str_replace().

It doesn't work - what now?

What if it doesn't work?
Go back and double check it.
Did you escape (insert backslashes) any double or single quote characters inside the text to find or insert?
Did you test it out first with a manual template edit to confirm your changes actually work?
Is your plugin actually enabled?
Does the hook you used run before your template is loaded?
Have you inserted any conditionals in the template text? Don't (not yet).
Are you sure the code in the plugin is running? Remove any conditionals you have around this code just to be extra sure that the str_replace is actually being executed.
Try simplifying what you are trying to do. Change the replacement to something really easy, just to confirm that str_replace() is actually running. Once it works, try to bring back your more complex replacement.
Did you have a syntax error in the plugin code you wrote? If so, the plugin will not have executed and the error will only be recorded in the error log of your webserver. You should check in this log to make sure it didn't generate an error. You can avoid this problem entirely by writing your php code first in a proper php editor with syntax checking built in. I use phpedit (http://www.waterproof.fr/).Where to now?

What's next? You know the basics, but what to do about more complex cases? You can check out a much more indepth look at the this subject in the advanced tutorial (http://www.vbulletin.org/forum/showthread.php?t=151332). How about tricks and tips for debugging when it doesn't work? Check out my debugging tutorial (http://www.vbulletin.org/forum/showthread.php?t=151288).

I encourage you to try this out for yourself and become comfortable with the technique. Do you have any older mods that required manual template edits? Why not revert those manual edits and try and make a plugin which does those edits automatically. If you succeed - share your plugin with the original mod author - I'm sure they will appreciate it.

Have you tried this technique successfully? Share your examples on this thread - more examples will make it easier for others to understand. Got a tricky example that you just can't get to work? Post it (after thoroughly trying to debug it yourself). I've written this tutorial because I couldn't find this information when I was learning. If this tutorial has helped you, then share what you've learned to help others.

12 Jul 2007, 07:13
Thank you for this nice tutorial.

Defenetly worth a bookmark for me ;)

Can't wait for your new tutorial "getting conditionals to work in plugins with str_replace" cause that is what I need :p



12 Jul 2007, 07:29
Can't wait for your new tutorial "getting conditionals to work in plugins with str_replace" cause that is what I need :p

Oops... I thought I'd added the link to part two to the end of this tutorial, but I now see that I haven't (have just done it now). You'll find part 2 here (http://www.vbulletin.org/forum/showthread.php?t=151332). The situation is a little trickier than I first thought, so I'll be updating part 2 with my new findings.

I'm also in the middle of writing an automatic tool to build these plugins automatically, but it's proving to be rather difficult...

28 Jul 2007, 06:07
Thanks mfyvie - good stuff!


27 Dec 2007, 06:41
thx for this tuto.... :up:

22 Jan 2008, 12:22
How to replace $currentuser with a custom template?

--------------- Added 1201094202 at 1201094202 ---------------

Nobody helps me? Sorry for my bad English. I would like to replace $currentuser with my custom template. Plz help me! Thanks

23 Jan 2008, 12:26
How to replace $currentuser with a custom template?

--------------- Added 23 Jan 2008 at 14:16 ---------------

Nobody helps me? Sorry for my bad English. I would like to replace $currentuser with my custom template. Plz help me! Thanks

Well I'm not sure what you are expecting - read the tutorial above - that's what it is about. Do you want me to type out the instructions a second time? The examples i've given even use $currentuser...

Is there some *aspect* of this tutorial which you think should be explained in more detail? If so, which?

But don't expect help if you ask such a broad or unclear question - sorry.

4x4 Mecca
11 Jun 2008, 16:54
Thanks! This helped me with my problems.

28 Apr 2010, 00:09
i'm trying to do this in vb4 with no luck. is there an updated tutorial? thanks

07 May 2010, 09:20
so am i m8 dont know its its possible anymore hope it is tho.

18 May 2010, 13:50
i was advised against trying to do it this way anymore.

using template hooks was recommended if possible. if not, manual template edit

18 May 2010, 15:05
It is still possible in vB4 using the same method. But, you need to realize that if a user changes their template and they change the line that is your $find variable, then this won't work. For that reason, it isn't always advised to use this method.

11 Oct 2010, 19:57
I am trying to do this to hide/remove a section from the template, but it does not work for me...

My code for the plugin is this:
$find = '<div class=\"block\" id=\"foldercontrols\">';
$replace = '/* ';
$vbulletin->templatecache['pm_messagelist'] = str_replace($find, $replace . $find, $vbulletin->templatecache['pm_messagelist']);

$find2 = '<div id=\"pmlist_info\" class=\"floatcontainer\">';
$replace2 = '*/ ';
$vbulletin->templatecache['pm_messagelist'] = str_replace($find2, $replace2 . $find2, $vbulletin->templatecache['pm_messagelist']);

and I am using hook: private_complete
I have also tried private_start

but there is no change to what is displayed
This should hide the PM Folder Controls, but they are still displayed without change.

11 Oct 2010, 21:59
I didn't think you could edit a template on the fly like that- I think you have to make an alternative template and use code to display that instead- at least that is how I always did it, which was once...:D

11 Oct 2010, 22:03
I didn't think you could edit a template on the fly like that- I think you have to make an alternative template and use code to display that instead- at least that is how I always did it, which was once...:D

that would be fine, how do I inject the new template into the existing one though, if a hook does not exist where I am trying to make the changes?

11 Oct 2010, 23:09
I don't know. :(

28 Nov 2010, 23:13
...but there is no change to what is displayed
This should hide the PM Folder Controls, but they are still displayed without change.

This was a long time ago now, but anyway - I think it may have been because you were inserting /* and */ instead of <!-- and -->

29 Dec 2010, 14:36
This was a long time ago now, but anyway - I think it may have been because you were inserting /* and */ instead of <!-- and -->

I wouldn't think so, as this plugin is changing the PHP code (unless I am understanding it wrong), but I will try that.

29 Dec 2010, 15:05
I wouldn't think so, as this plugin is changing the PHP code (unless I am understanding it wrong), but I will try that.

I see what you mean, but in that case it seems like your replacements would end up part of a string constant instead of outside of any string (where they'd need to be to work as comment delimiters). But I could be wrong. It might help to somehow look at the exact string in the template cache.