HTML Logo by World Wide Web Consortium (www.w3.org). Click to learn more about our commitment to accessibility and standards.

ocPortal Tutorial: Tempcode programming

Written by Chris Graham, ocProducts
Tempcode is ocPortal's template programming language. On the simplest level, it provides a substitution mechanism so that parameters and global symbols (like the current user's username, or the time) can be inserted into a template. It also serves as a complete programming language with a powerful set of control mechanisms.



Philosophy

Most templating languages will try to either:
  • use XML, to add programming constructs into the mark-up itself
  • allow PHP code, or an abstraction of PHP code, to run within the templates

Our approach is distinctly different, as we uphold four strong principles:
  • templates should be editable in an HTML editor without Tempcode being broken (although the HTML editor at this time must be able to support 'fragments of HTML')
  • templates should not contain code on the same level as PHP, for security reasons (a theme should not be able to delete files, for instance)
  • there should be a very clear separation of role between templates and code; code should not be put into templates simply because it is convenient, as this muddies the architecture, causing maintenance issues
  • templates should not 'execute': Tempcode should act as a filter/token-placer for output, reshaping output, rather than intertwining code with it. This is again, an architectural issue

Due to our design principles, Tempcode is a very unusual language, with a very simple syntax.

Advanced philosophy (experts only)

For those interested in 'programming language semantics', Tempcode is neither a functional or imperative language, but has aspects of both. A functional language essentially is focused on output being controlled by a complex mathematical expression. An imperative language essentially is focused on output being controlled by a sequence of commands. With Tempcode, output is focused on the stream of text coming from a template, and the composition of these streams; 'symbols' and 'directives' can control, manipulate and add to the stream, but they are more like embedded functional-language function-chains and control-tags than imperative code. Sequences of commands can be simulated by placing symbols and directives next to each other in the output stream and using variable manipulation features to bridge data across the portions of the output stream.

Popular template languages such as 'Velocity' or 'Smarty' are able (and often used) to function in a similar sense to this, but their language design is imperative at core, and the result simply is just far less elegant. Tempcode just 'feels right', with its clean syntax and PHP-separation, whilst other languages look literally like conventional programming code has been added inside the template in an ad-hoc way (even if the full programming syntax has been simplified a bit).

Syntax

Tempcode provides four constructs other than the plain text of the template itself:
  • Parameters: {PARAMETER} where PARAMETER is a code-name of something that was actually passed to the template.
  • Language strings: {!STRING,<parameters>...} where STRING is a real language string from a loaded language file (e.g. Banner code could use strings from banners.ini, but most other code could not; global.ini contains strings usable anywhere)
  • Symbols: {$SYMBOL,<parameters>...} where SYMBOL is a real symbol ocPortal supports. Embedding a symbol is like making a function call or running a command in the complex place, or a substitution in the simple case.
  • Directives: {+START,DIRECTIVE,<parameters>...}...{+END,DIRECTIVE} where DIRECTIVE is a real directive ocPortal supports. Directives essentially wrap portions of the template, controlling that portion; they are generally used for types of condition checking (e.g. IF) or loop. There are also directives like {+IMPLODE,<parameters>...} that do not wrap, but work outside the normal "string manipulation" bounds that symbols do.

Any Tempcode construct may be escaped (made to fit in an encoding scheme, such as HTML or  URLs, such that special text characters do not interact with that encoding scheme in an insecure/corrupting way) by escaping filters with ease, just by placing the symbol associated with the mode of escaping before the closing '}'. The following escaping filters are provided:
  • (*) HTML  (e.g. Hello & Goodbye  –>  Hello & Goodbye)
  • (;) Between single quotes   (e.g. Who's here  –>  Who\'s here)
  • (#) Between double quotes   (e.g. She said, "Hello"  –>  She said, "Hello")
  • (~) Where new lines are not allowed   (text is drawn up to reside on a single line)
  • (@) Comcode   (e.g. Use the [url] tag  –>  Use the \[url] tag)
  • (/) Special Javascript SGML-issue   (e.g. print('</p>');  –>  print('<\/p'))
  • (^) Where new lines become "\n" (multiple lines drawn together with \n as a separator)
  • (|) Javascript Ids   (e.g. This is a -terrible- ID  –>  This__is__a____terrible____ID)
  • (%) Strict codenames   (e.g. ThiS –> ThiS   but  This is  –> hack attack )
 
It is absolutely crucial that Tempcode programmers use the proper escaping. Without it, all kinds of insecurities and unreliabilities can develop. About 50% of parameters in the default ocPortal templates actually use HTML escaping so that plain text placed inside a template does not interfere with the HTML structure itself and displays literally. More information on this is provided in the dev-guide.

The Tempcode tree

Templates are composed together into a tree structure, and then the tree structure is output. This is discussed in further detail in the 'site structure' tutorial.

Numbers and logic

Tempcode is an entirely string based language. It does, however, provide significant logic and number functionality, which is made possible by conversion from text as symbols require. Numbers are read directly, and should not contain any special formatting other than the British English decimal point (.) symbol. 'True' is encoded as '_true' and 'False' is encoded as '_false'.

Shift encoding

Shift encoding is a very powerful technique made possible through symbols and directives. Using shift encoding it is possible to move text from one template onto another - hence breaking through the walls of the tree structure that the ocPortal template system has.

To shift encode a portion of a template, it just needs to be wrapped with the SHIFT_ENCODE directive and given a name as a parameter to the directive. To shift decode a potion, it is just extracted using the SHIFT_DECODE symbol and it's name.

Symbols

A comprehensive explanation of all symbols would go on for many pages, so I will only summarise what is available here. As this is a form of programming, I hope you are comfortable with looking at PHP source code to find exact implementation details (symbols.php). In addition, the template editor provides some additional aid.

Symbol name Purpose
(blank) Place a comment. E.g.... {$,this is a comment}
? Do an inline IF test, outputting the second parameter if the first is true and the third (optional parameter) if it is not
BLOCK Place a block into the template
INCLUDE_PAGE Load a page into a template
LOAD_PANEL Load a panel-page into a template
NO_SAFE_MODE Detect if PHP safe mode isn't on the server
KEEP Stuff to append to an ocPortal URL for keep parameter passing
KEEP_INDEX As above, except this includes index.php as well as the parameters
BANNER Display a banner of specific configuration (dimensions and type)
IMG Find a named theme image code's URL
THEME The user's current theme
RUNNING_SCRIPT Find whether an entry script is running
URLISE_LANG Allows language strings to wrap links without putting HTML in them
FIND_SCRIPT Find the URL to a named ocPortal entry script
LANG The user's current language
AVATAR The member's avatar
BASE_URL The base URL to the installation
THIN_NEEDED Whether a PDA (for example) is being used
ANCHOR Place a named HTML anchor (intended for use from in Comcode)
RAND A random number
SET_RAND A random choice from the given parameters
SITE_NAME The site name
COPYRIGHT The site copyright
STAFF_ADDRESS_PURE The staff address, without obfuscation
STAFF_ADDRESS The obfuscated staff address
DOMAIN The domain name of the site
CSS_TEMPCODE Get the Tempcode for all the CSS includes for this page
JS_TEMPCODE Get the Tempcode for all the JS includes for this page
CSS_DIMENSION_REDUCE Reduce a CSS dimension by a certain number of px. If the input is not in px, no function is performed and the output stays as the input.
ZONE The zone the user is in
PAGE The page the user is in
MATCH_KEY_MATCH Whether the given match key matches the current URL
MANAGEMENT_NAVIGATION The management navigation menu
VERSION The major version of ocPortal being used
USER The Member-ID
USER_OVERIDE The Member-ID of the Personal Zone being viewed
USERNAME The username of the current user / given user
IS_STAFF Is the current user / given user staff
IS_SUPER_ADMIN Is the current user / given user a super admin
CYCLE Cycle through a named list of strings (intended for use across template calls to create, for example, striping effects)
SITE_SCOPE The site scope
OCF Whether OCF is being used
BOARD_PREFIX The forum base URL
DATE_AND_TIME The formatted data and time
DATE The formatted date
TIME The formatted time
FROM_TIMESTAMP Converts a time-stamp to a formatted date/time
IN_ARRAY Whether the first parameter is one of the remaining parameters
JS_ON Whether JS is enabled
JAVASCRIPT_INCLUDE Include a Javascript file in the output stream
CSS_INCLUDE Include a CSS file in the output stream
SEARCH_TUTORIAL If a tutorial covering the given subject exists, link to it
BROWSER_MATCHES Find whether the current browser matches a named property
BROWSER Conditionally chooses between two input strings, based on a browser property
IS_ON_POINTS Find whether points are enabled
SHOW_PREVIEWS Find whether the user has forced previews to show
PREVIEW_URL The URL to get a preview of a form on the current page
OCP_ENTERPRISE Whether this is the enterprise version of ocPortal
MULT Multiply two numbers
ROUND Round a number
ISSET Find whether a Tempcode variable is set (not a construct)
INIT Initialise a Tempcode variable to a value, but only if it is not yet set
SET Set a Tempcode variable to a value
GET Get the value of a Tempcode variable
INC Increment a Tempcode variable
DEC Decrement a Tempcode variable
MAX Find the maximum between two numbers
MIN Find the minimum between two numbers
MOD Make the given number positive (e.g. -3 becomes 3, 3 becomes 3)
REM Find the remainder if the first number is divided by the second
DIV Divide two numbers
SUBTRACT Perform a subtraction
ADD Add two numbers
WCASE LCASE UCASE Convert string case
_POST _GET Extract script parameters
REPLACE Do a string replace
AT Extract a string character
STRPOS Find the position of a string sub-string
IN_STR Find whether a sub-string is in a string
SUBSTR Extract a string sub-string
LENGTH Find the length of a string
WORDWRAP Word-wrap a string
ALTERNATOR_TRUNCATED A complex symbol to allow alternation between situations when something would and would not become truncated.
TRUNCATE_LEFT TRUNCATE_RIGHT TRUNCATE_SPREAD Truncate a string. Optional support for trying to fudge valid truncation of an HTML string. Optional support for tool-tips for mouse-over on truncated strings.
ESCAPE Perform escaping. Uses the same escaping as integrated template escaping, except constants are used to determine the escaping, rather than special characters. Defaults to HTML escaping.
IS_NON_EMPTY Find whether a string is not equal to ''
IS_EMPTY Find whether a string is equal to ''
IS_IN_GROUP Find whether a member is in a user-group
NEGATE Negate a number
NOT Boolean NOT
OR Boolean OR
NOR Boolean NOR
XOR Boolean XOR
NAND Boolean NAND
AND Boolean AND
EQ Test for string equality
NEQ Test for string inequality
LT Test for numerical less than inequality
GT Test for numerical greater than inequality
SELF_URL Get URL to current screen
COOKIE_PATH Get the cookie save path
COOKIE_DOMAIN Get the cookie save domain
IS_A_COOKIE_LOGIN Find if a cookie login is active
OBFUSCATE Obfuscate a string to make it harder for e-mail scavengers
FIX_ID Perform escaping similar to ID escaping
MAILTO Get an obfuscated "mailto: " string
ATTACHMENT_DOWNLOADS Find how many downloads an attachment has had
PAGE_LINK Link to a page, using [concept]page link[/concept] syntax
SHIFT_DECODE Shift decode a named shift encoded lump of Tempcode
HAS_SPECIFIC_PERMISSION Has a specific permission
HAS_ZONE_ACCESS Has access to a zone
HAS_PAGE_ACCESS Has access to a page (but NOT necessarily the zone it is in)
HAS_ACTUAL_PAGE_ACCESS Has access to a page and it's zone
HAS_CATEGORY_ACCESS Has access to a category
HAS_ATTACHMENT_ACCESS Has access to an attachment
HAS_SUBMIT_PERMISSION Has submit permission
HAS_DELETE_PERMISSION Has delete permission
HAS_EDIT_PERMISSION Has edit permission


If a symbol is missing important parameters, it generally is skipped.

Directives

Directive name Purpose
BOX These allow the 'standard box' layout convention to be used without duplicating a lot of HTML in different templates. CSS could never provide this itself, as the box 'styling' actually involves a large chunk of complex structure.
The parameters to the directive are (all may be blank or left out): the title (blank means no title is used), the width/height classification (e.g. 100%, 100%|300px, ...), the type of the table (there will be a template, STANDARDBOX_<type> for any valid type code; he default is 'classic'), '|' separated list of options (meaning dependant upon templates interpretation), '|' separated list of meta information (key|value|key|value|...), '|' separated list of link information (linkhtml|...).
IN_ARRAY/NOT_IN_ARRAY/IF_IN_ARRAY/IF_NOT_IN_ARRAY See if something is contained in an array
INCLUDE Include a template in another and pass parameters (this makes a template very much like a function in a conventional programming language)
WHILE/LOOP Do a loop over a list of map arrays. This is a very complex directive, and not currently used in default templates. If has support for automatic columnisation and sorting.
IF_PASSED/IF_NON_PASSED Find if a named parameter was passed (Do not encase the parameter with {})
IF_EMPTY/IF_NON_EMPTY Find if some Tempcode evaluates to empty. This is extremely useful, as it allows you to, for example, now show a table if it has no rows passed in. From a programming point of view, it allows us to avoid making new templates and improve accessibility compliance.
IF_ARRAY_EMPTY/IF_ARRAY_NON_EMPTY Find if an array is empty.
IF_ADJACENT/IF_NON_ADJACENT This allows templates to be context dependant. For example, if a template starts of using a <li> tag to make a list, but someone wants to change it to simply put "|" characters between entries, then IF_ADJACENT could be used so as to not add a "|" at the start of the first entry in the lit.
IF This simply tests a condition. This condition is often generated by nesting symbols together. For example, {+START,IF,{$EQ,{A},{B}}}...{+END}
SHIFT_ENCODE Shift encoding, as described earlier.
OF This is a non-wrapping directive that extracts a string from an array at a certain array position.
IMPLODE This is a non-wrapping directive that turns an array into a string by placing a separator between each element.
COUNT Find the number of elements in an array.
FRACTIONAL_EDITABLE Used to make a string of some content editable inline (usually, the title as shown in the index).


Examples

See tempcode_test.tpl for examples on how to use symbols and directives.


Concepts

symbol
A special element that may be inserted into
parameter
A parameter to a template; only parameters that the code calling the template supports may be used
directive
A tool used to surround an area of a template between a START and an END, to apply something to it (such as only using it conditionally)
Shift Encoding
The process of identifying a portion of a template to shift into another
Shift Decoding
The process of taking an identified template portion and placing it in the referencing template
variable
Two uses: 1) Either of a parameter, symbol, directive or language string reference. 2) A named piece of memory used by Tempcode symbols that manipulate variables.

GUIDs

There is a design issue that comes to us when we design template structure… do we re-use templates so that editing is easier, load is decreased, and consistency is raised; or do we allow maximum customisation of different aspects of the system by not re-using templates?
We have tended to stick to a middle ground between these extremes, and re-used templates when the usage pattern was almost identical. For example, almost all of ocPortal uses the same form field templates, but CEDI posts use different templates to forum posts. However, there are still places where we re-use templates in situations that sites may wish to develop in separate directions.

The solution to this is to make use of the GUID each template usage is given when it is used.
Each 'do_template' call in ocPortal, which loads up a template, passes in a parameter, '_GUID'. The GUIDs may then be used along with tempcode directives to control what the template outputs.
The template editor has special support for the GUIDs, showing what GUIDs a template is used with and providing links to view the code associated with each GUID. It also provides a feature to automatically insert an 'IF' directive that differentiates against one of the GUIDs to provide a place to put output that will only be shown for it.

If you enter the template editor from the 'Template tree' screen then you will be told the GUID that was used by the node in the tree that you selected.

See also