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.Table of contents
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.



