Caching Template Library

From GeeklogWiki
Revision as of 14:07, 30 November 2018 by Tom (talk | contribs) (Example 4)

Jump to: navigation, search

Introduction

The Caching Template Library (CTL) is the template engine in Geeklog. A template engine facilitates a manageable way to separate application logic and content from its presentation. This allows the programmer to focus specifically on the application and the template designer to focus on the presentation. It also allows site administrators to easily manipulate the look and feel of their site without having to know the application or become a programmer.

The benefits of the Caching Template Library are that it adds the following features to Geeklog. These features benefit both the site administrator and the plugin developer.

  • Compiles templates to PHP code for enhanced page load speeds.
  • Adds logic processing to the templates.
  • Ability to specify multiple locations to search for templates.

Compiles Templates to PHP code

One of the unique aspects about the Caching Template Library is template compiling. This means the Caching Template Library reads the template files and creates PHP scripts from them. Once they are created, they are executed from then on. Therefore there is no costly template file parsing for each request. Each template can take full advantage of the PHP compiler and cache solutions such as eAccelerator, ionCube mmCache or Zend Accelerator to name a few. Some anecdotal experience with performance testing can be found in the forum.

Logic Processing

One design goal of the Caching Template Library is the separation of application logic and presentation logic. This means templates can certainly contain logic under the condition that it is for presentation only. Things such as checking if a specific variable is set and adjusting the display appropriately is a good example of presentation logic. Also, if you desire no logic in your templates you certainly can do so by boiling the content down to text and variables only.


Not just for Templates

As of Geeklog v2.1.3 you can set a View instead of a template file when using the Caching Template Library. A View is basically a large string of text that can be compiled by the Caching Template Library. The text string can there for contain the same type of logic processing, template variables and autotags as a normal template file. With the view, developers can even assign their own template variables or access the default ones automatically like site_url, anonymous_user, and device_mobile. The first plugin to support this is the Static Pages Plugin v1.6.9. For more information on how this works see Static Pages plugin wiki page.

Template Blocks are supported via a View as well. The only thing that is not supported for Views is the ability to have a Template Block within another Template Block.

Multiple Template Source Paths

There are many templates files within the Geeklog system, plus all the template files that plugins use as well. If you want to change the look and feel of a site, generally only a small number of template files are actually modified. Having the ability to specify multiple templates paths allows you to have a base location for templates, and then simply copy the ones you wish to modify to an alternative location (usually the /custom folder in the same directory.) This reduces the overall disk storage needed on the server, and also provides a method to quickly see which templates have been modified. It also means that your modified template files will not be overwritten during a software upgrade.

Plugins can also support multiple template paths which allows for template files to be included with a plugin install for more than one theme. Currently the core plugins, media gallery and the forum support this. For more information on this and how to implement it please read Theme Specific Plugin Templates.

Benefits

Benefits of the Caching Template Library include:

  • It is extremely fast.
  • It is efficient since the PHP parser does the dirty work.
  • No template parsing overhead, only compiles once.
  • It is smart about recompiling only the template files that have changed.
  • The {!if}..{!else}..{!endif} constructs are passed to the PHP parser, so the {!if…} expression syntax can be as simple or as complex an evaluation as you like.
  • It is possible to embed PHP code right in your template files, although this may not be needed (nor recommended) since the engine is so customizable.
  • Built-in caching support.
  • Multiple template sources.

Overall, the Caching Template Library brings a significant amount of value and features to a Geeklog site.

Developer Information

Template Variable Naming Convention

Template variables follow a strict naming convention. A template variable consist of letters, digits, one or more underscore, period, dash or square brackets, and are delimited by curly brackets. Examples can be found in Geeklog theme files (files with the extension thtml).

{template_variable}

If any other character is found within curly brackets then the text is not considered a template variable at all, and therefor doesn’t follow the template class rules for unknown setting of remove, comment or keep. The text will just be left as is.

There are a few special cases where a '$' and ':' may be used in a template variable. This includes the use of the Automatic Language File Variables and the Variable Manipulation Modifiers.

Version Checks

You can tell that the caching template library is installed and what version of the library is running by checking if TEMPLATE_VERSION is defined using the PHP function defined().

TEMPLATE_OPTIONS

At the top of the file is an array of TEMPLATE_OPTIONS. These options are global options for all templates created by Geeklog. They are:

'path_cache' (required)

This option points to the cache directory. If you don't like the name “layout_cache”, this is where you change it. By default it is equal to “$_CONF['path_data']”.”layout_cache/”.

'path_prefixes' (required)

This is a list of ALL paths under which templates may be found. The cached name of the template file is based on the path of the template file. These array entries are used to strip off the redundant portions of the path. The order they appear here is important because once a match is found other prefixes will not be checked. Basically they should be included in this entry with the longest paths going first.

By default, the options are:

  • the root of your themes directories ($_CONF['path_html']/layout).
  • the root of your plugins directory ($_CONF['path']/plugins).
  • the root of your server ('/') Do not remove this one!!

In a hosted environment, you might want to add your account's home directory before the last entry.

'unknowns' (optional)

Sets the default unknown handler. If it isn't set, default unknowns become 'remove', as usual. The other options are 'comment' and 'keep'.

'force_unknowns' (optional)

Sets the unknown handler regardless of the calling code's settings. This is useful for debugging a set of templates as you only have to modify the template class in one place.

'default_vars' (optional)

This is a list of template variables that is set for every instance of the template object. The most obvious use for this is stuff like $_CONF['site_url']. This way the theme author doesn't have to rely on the dev team remembering to include such variables in every template.

The default values are:

  • 'site_url' ⇒ $_CONF['site_url']
  • 'site_admin_url' ⇒ $_CONF['site_admin_url']
  • 'layout_url' ⇒ $_CONF['layout_url']
  • 'XHTML' ⇒ XHTML
  • 'anonymous_user' ⇒ COM_isAnonUser(),
  • 'device_mobile' ⇒ $_DEVICE->is_mobile(),
  • 'front_page' ⇒ COM_onFrontpage(),
  • 'current_url' ⇒ COM_getCurrentURL() (Note: This returns the current URL as PHP sees it on the server. If the server is setup to rewrite the URL then it may be different from the actual URL)

'incl_phpself_header' (optional)

This boolean option is used to control the inclusion of anti-spoofing text in the resulting cache files. It defaults to true. If your cache directories can be accessed by remote browser (because you cannot create files outside your webroot) you must set this value to true or risk a security issue with your cache files.

When true, the following is added to the top of every cached template file with filename replaced with the current filename:

<?php if (strpos($_SERVER['PHP_SELF'], basename($filename)) !== false) {
    die ('This file can not be used on its own.');
} ?>

'cache_by_language' (optional)

This boolean variable determines whether or not to create unique cache files per language instance. When cache_by_language is on, a directory is created under the data/layout_cache directory for each language enabled and accessed on your website. Templates that take advantage of the Automatic Language file variables features are described below. These variables are replaced when the cache file is created instead of dynamically each time the cache is hit.

Unless you have tight filesystem restrictions, you should set this variable to true to maximize your potential system speed up.

'hook' (optional)

This advanced feature is designed for use by developers and theme makers. It is described more fully elsewhere in this documentation. It provides ways for a developer to hook calls to various methods on the Template class in order to modify the generated output without modifying the code creating the output.

TEMPLATE->set_root()

The set_root method now accepts an array of root directories. When files are added to the class using set_file, the files are checked against the array of root directories in order. The first path listed overrides subsequent paths. So the most common use for multiple roots is in plugins that might be themed. The theme directory should come first followed by the plugin's template directory. That way the theme's file take precedence over the plugin's default templates.

Plugins typically create templates using a function to guess at the correct path:

        function calendar_templatePath ($path = '')
        {
            global $_CONF;
 
            if (empty ($path)) {
                $layout_path = $_CONF['path_layout'] . 'calendar';
            } else {
                $layout_path = $_CONF['path_layout'] . 'calendar/' . $path;
            }
 
            if (is_dir ($layout_path)) {
                $retval = $layout_path;
            } else {
                $retval = $_CONF['path'] . 'plugins/calendar/templates';
                if (!empty ($path)) {
                    $retval .= '/' . $path;
                }
            }
 
            return $retval;
        }
 
        $template = new Template(calendar_templatePath('additional/path'));

This is potentially a problem if the theme doesn't copy over all the files needed by set_file. Now this is possible:

        $template = new Template(
                        array($_CONF['path_layout'].'calendar/additional/path',
                              $_CONF['path'].'plugins/calendar/templates/additional/path'
                             )
                  );

or

        function calendar_templatePath($path = '')
        {
            global $_CONF;
 
            $layout_path = $_CONF['path_layout'] . 'calendar';
            $plugin_path = $_CONF['path'] . 'plugins/calendar/templates';
            if (!empty($path)) {
                $layout_path .= '/' . $path;
                $plugin_path .= '/' . $path;
            }
 
            return Array($layout_path, $plugin_path);
        }
 
        $template = new Template(calendar_templatePath('additional/path'));

Another benefit of this is that if you only want to modify a subset of a plugin's templates for your theme, you only need to put the subset of files in the theme directory. Having multiple root directories means any files not in the themes directory are taken from the default directory.

Replacement Variable Manipulation Modifiers

Modifiers can be applied to replacement variables using the format {variable:m} where variable is a normal replacement variable and m is the modifier. Multiple modifiers can be applied in series {variable:u:s}. The following modifiers currently exist:

  •  :u Call urlencode on the variable before output.
  •  :s Call htmlspecialchars on the variable before output.
  •  :h Call strip_tags on the variable before output. (as of Geeklog v2.2.1)
  •  :t### Truncates the variable to ### characters.

Using these options should be done with care, making sure the calling code hasn't already applied an output filter to the data. Likewise, these calls should not be used on any variable containing HTML.

Automatic Language File Variables

Add any text you want to the template while still following the Geeklog text internationaliztion guidelines. Any variable in the form {$LANG_abc[xx]} will lookup the index in the stated language array. So {$LANG_ADMIN['save']} will be replaced by the same text as that placed on all the Save buttons in the system. Template files are no longer limited to the text labels delivered by the coder. Text from the language variable is automatically passed through htmlspecialchars before output. Language variables cannot be used in simple action variables. Advanced action variables can use them.

// code like this
  $T->set_var('lang_username',$LANG_USER['name']);
  $T->set_var('username', $username);

// becomes
  $T->set_var('username', $username);

// and the template
<tr><td>{lang_username}:</td><td>{username}</td></tr>

// becomes
<tr><td>{$LANG_USER[name]}:</td><td>{username}</td></tr>

There are plans to do research at some future date into which is faster, setting a variable from a LANG array, or putting the LANG array reference directly into the template.

Template Blocks

One way to save on having a large number of template files is use template blocks. 1 or more blocks can exist inside a template file and these blocks can also be nested (blocks within blocks). In the example below the template file contains a select control called "select1" with a template variable called "options". There is also a template block called "select-option" with some other variable names.

<select name="select1">
{options}
</select>

<!-- BEGIN select-option -->
<option value="{value}" {selected}>{option_name}</option>
<!-- END select-option -->

The sample PHP code below makes use of this template and it's block and loads in some options from the database.

$t = COM_newTemplate($_CONF['path_layout']);
$t ->set_file('control', 'control.thtml');
$t ->set_block('control', 'select-options'); 

$selectedvalue = 2;
$sql = "SELECT * FROM some_table";
$result = DB_query($sql);
$numRows = DB_numRows($result);
for ($i = 0; $i < $numRows; $i++) {
    $A = DB_fetchArray($result, true);
    $t ->set_var('value', $A['somevalue']);
    $t ->set_var('option_name', $A['somename']);
    if ($A['somevalue'] == $selectedvalue) {
        $t ->set_var('selected', 'selected="selected"');
    } else {
        $t ->set_var('selected', '');
    }
    $t->parse('options', 'select-option', true);
}

$t->finish($t->parse('output', 'control'));

Logic Processing

One of the biggest benefits of the Template Caching Library is the ability to place logic into the templates. This section assumes you know how to use the old template library.

Usage

The template library uses variable substitution in template files to create output. All template constructs are contained within braces: {variable}. The simplest construct is the variable as just shown. The new library adds action variables to the mix. Actions are also contained within braces and the first character of an action is an exclaimation point: {!action parameters}. Actions usually do not output anything to the final output. Instead they control what is output around them. In these cases, the action will act as a block within the template contained between two actions. The second action is usually the same as the first with the word 'end' prepended: {!endaction}.

CTL v2.2 introduced advanced actions. These have the format {!!action parameter !!}. The space and exclamation point are required. Advanced actions allow you to place more complicated values as the parameter field. Instead of assuming parameters are template variables, advanced action parameters that reference template variables must include the braces just like they do in other parts of the template. So while a simple if might look like this {!if var}, the advanced if can look like this {!!if {var} == 'a' || {var} == 'b' !!} (note the number of exclamation points) Advanced actions are closed by standard {!endaction} constructs. (note: one exclamation point). Complex conditions can also contain calls to any global function and if you want you can also include template variables within the function call (ie as a function variable).

In this example we use a template variable {uid} (defined by some other process) in an Advanced Action. We use it to call the function COM_isAnonUser to see if the id is the anonymous Geeklog user:

{!!if COM_isAnonUser({uid}) !!}
<p>User {uid} is an anonymous user.</p>
{!else}
<p>User {uid} is NOT an anonymous user.</p>
{!endif}

Actions

Simple Actions

Action Description
{!if variable}

{!endif}

The !if action shows the contained block if the variable evaluates to true. Note that the value 0 evaluates to true.
{!else} This action must appear between !if and !endif. It breaks the !if block into true and false halves. The block from !if to !else contains the text displayed if the condition is true. The block between !else and !endif contains the text displayed if the condition is false.
{!elseif variable} This combines else and if together to allow multiple possible conditions to be evaluated.

Looping Actions

Action Description
{!while variable}

{!endwhile}

Similar to if, the contained block is displayed repeatedly as long as variable is true. If the variable starts out false the block is never displayed.
{!loop variable}

{!endloop}

Creates a variable called variable__loopvar. The contained block is executed 'variable' times with varialbe__loopvar counting from 1 to 'variable' for each iteration. If variable is less than zero, counting goes downward starting at -1. variable__loopvar is deleted (unset) when the loop exits.
{!break} Exits a loop prematurely. The rest of the block is not processed and processing continues after the !endwhile or !endloop.
{!continue} Ends a loop block prematurely. The rest of the block is not processed and processing continues for the next iteration of the loop.

Other Actions

Action Description
{!inc variable} The variable is incremented by 1. If it is a non-numeric string, the value '1' is placed in it.
{!dec variable} The variable is decremented by 1. If it is a non-numeric string, the value '-1' is placed in it.
{!inc+echo variable} As !inc and the number is displayed.
{!dec+echo variable} As !dec and the number is displayed.
{!unset variable} Removes the variable from the template's internal variable list.

Advanced Actions

Action Description
{!!if condition !!} The condition may contain any template construct that does not end with !}.
{!!while condition !!} Works like the simple {!while var} but can take any complex condition.
{!!global var,… !!} Pulls the global vars into the cached PHP output.
{!!echo condition !!} Echo's the complex condition to the cached PHP output.
{!!set var value/expression !!} Assign the variable the listed value or set any complex expression or condition.
{!!autotag ... !!} Works like so {!!autotag story:welcome !!} (this is for autotag [story:welcome]) and allows you to set an autotag in a template file.

Works with autotags that have a close tag as well but you must encompass the entire autotag (including the close tag) in one action and include one half of the square brackets for example [tag:foo]some text here[/tag] would become {!!autotag tag:foo]some text here[/tag !!} in the template.

Note: You cannot embed template variables within autotags or any other Actions.

Comments

You can include comments in the template files that do not appear in the cached PHP file by enclosing the comment within {# and #} symbols. This is useful for explaining why you have some weird construct in your code without cluttering the cached template with lots of HTML comments. If you want these comments to appear in the cache and the HTML source of the page you can in the Geeklog Configuration enable "Template Comments in Output". See example 3 to see how to use a comment.

Examples

Example 1

In this example we use a template variable in an Advanced Action if, elseif, and else condition:

{!!if {display_type} == '12' !!}
<p>Display this text when display_type template variable equals 12.</p>
{!!elseif {display_type} == 'yawn' !!}
<p>Display this text when display_type template variable equals yawn.</p>
{!else}
<p>Display this text when display_type template variable is anything else.</p>
{!endif}

Example 2

In this example we use a template variable in an Advanced Action if condition:

{!!if {template_var} == 'info' OR {template_var} == 'info2' !!}
<p>Display this text when template_var equals info or info2.</p>
{!endif}

Example 3

In this (contrived) example, the only template variable is 'count' and it is set to 3:

{# This is an example of a comment #}

{!!set count 3 !!}

{!loop count}
{count__loopvar} of {count}: {!inc+echo total}
{!!if count__loopvar == "2" !!}{!inc count}{!endif}<br>
{!endloop}

If the template is parsed twice without resetting count between parses, the output looks like this:

1 of 3: 1
2 of 3: 2
3 of 4: 3
4 of 4: 4
1 of 4: 5
2 of 4: 6
3 of 5: 7
4 of 5: 8
5 of 5: 9

Example 4

In this example, we set the array myVar and then print it out using a while loop:

{!!set myVar array('a', 'b') !!}
{!!set myCount 0 !!}

{!!while !empty(array_slice({myVar}, {myCount}, 1)) !!}
    {!!set myVar1 array_slice({myVar}, {myCount}, 1)[0] !!}
    myVar: {myVar1}<br>
   {!inc myCount}
{!endwhile!}

The output then looks like this:

myVar: a
myVar: b

Example 5

This example is Geeklog specific to help illustrate how you might use even the simplest !if construct:

if ( $_USER['uid'] >= 2 ) {  // user is logged in...
    $T->set_var('onlyloggedinusers', 'Some feature only for logged in users');
}

In the template, you can now check whether the user is logged in by checking for the existence of the {onlyloggedinusers} variable:

<div id="pageheader">
  Show the page header
</div>
{!if onlyloggedinusers}
  <div class="boldtext">Logged in users can do more here, thanks for logging in!</div>
{!else}
  <div class="boldtext">If you login, you can do more!</div>
{!endif}

This is a simple example, but it does illustrate the capability and the power. No longer in the PHP code do you need to handle the non-logged in case.

PHP Code in a Template Files

This feature allows you to embed PHP code directly in templates. To embed PHP code directly in a template, use the following example:

<div class="welcomeanddate-text">
  <span class="gl_user-menu-right">
<?php global $_USER, $_CONF; if (isset($_USER['uid']) && $_USER['uid'] > 1) { ?>
    <a href="{site_url}/usersettings.php?mode=edit">{$LANG01[48]}</a><br{xhtml}>
    <a href="{site_url}/users.php?mode=logout">{$LANG01[35]}</a>
<?php } else {
if ($_CONF['disable_new_user_registration']==0) {?>
   <a href="{site_url}/users.php?mode=new">{$LANG04[27]}</a><br{xhtml}>
<?php } ?>
   <a href="{site_url}/users.php?mode=login">{$LANG01[58]}</a>
<?php } ?>

Notice how all PHP is surrounded by <?php php_code_here  ?>

Here is another example that will return the template variable 'title' from the template class (if it is set).

<?php
    echo $this->get_var('title');
?>

Troubleshooting

The most common problem you will run into with the template cache is forgetting they are there. If you go into a .thtml file and make a change and it isn't showing up, you may need to delete the cached file as the algorithm to overwrite them only works when the file dates are correct. Some FTP systems and web consoles screw up the file times of uploaded files preventing the class from updating the cached PHP file. To ease this process, there is an entry in the Admin Command & Control screen labeled Clear Template Cache.

Tip 'n Tricks

The Caching Template Class works with variables and array's may be used in its interface (API). You may encounter a GOTCHA when you mix simple arrays and hashes in some places.

The set_var call has two forms: set_var($var_name, $var_value) and set_var($var_names_array). This array is definitely a hash (a key-value pair). The get_var call has two forms: get_var($var_name) and get_var($var_names_array). This array is definitely a simple array (numeric keys). So, setting variables using set_var($myArray) would work, while get_var($myArray) would NOT work. Instead use get_var(array_keys($myArray)). The API call get_vars however is suited to accept a hash, but only a hash.

Same goes for clear_var($myArray). One easily mistakes here when processing data from mySql as rows: set_var($row) and clear_var(array_keys($row)). And now, there is no API call clear_vars available.


Using php code in a template, the following could be useful:

<?php $this->set_var('newvar', 'newvalue'); ?>

Using php code in a template, a global variable could be used to hand over data to the next template in a chain of templates. In case the template does specific calculations, generates a graph, or else. This comes in handy with auto tags.

<?php $GLOBALS['myVar'] = $this->get_var('some_variable'); ?>

<?php $this->set_var('some_variable', $GLOBALS['myVar']) ?>

This page is based on original content from the glFusion Wiki - https://www.glfusion.org/wiki/glfusion:development:usingtemplates - modified for Geeklog - Licensed under CC Attribution-Share Alike v4.0.