Login / Status
developer.Resource
Home . Documentation . Document Library . Core Documentation
Sponsors
hosted by punkt.deTYPO3 and Open Source MagazineAOE Media

1.4. The TypoScript parser  API

Introduction

If you want to deploy TypoScript in your own TYPO3 applications it is really easy. The TypoScript parser is readily available to you and the only thing that may take a little more effort that instantiation of a PHP is if you want to define conditions for TypoScript.

Basically this chapter will teach you how you can parse you own TypoScript strings into a PHP array structure. The exercise might even help you to further understand the straight forward nature of TypoScript.

Notice that the following pages are for experienced TYPO3 developers and requires a good knowledge of PHP.

Parsing custom TypoScript

Lets imagine that you have created an application in TYPO3, for example a plugin. You have defined certain parameters editable directly in the form fields of the plugin content element. However you want advanced users to be able to set up more detailed parameters but instead of adding a host of such detailed options to the interface - which would just clutter it all up - you rather want advanced users to have a text area field into which they can enter configuration codes based on a little reference you make for them.

The reference could look like this:

Root level

Property

Datatype

Description

Default

colors

->COLORS

Defining colors for various elements.

adminInfo

->ADMINFO

Define administrator contact information for cc-emails

headerImage

file-reference

A reference to an image file relative to the websites path (PATH_site)

->COLORS

Property

Datatype

Description

Default

backgroundColor

HTML-color

The background color of blablabla...

white

fontColor

HTML-color

The font color of text in blablabla...

black

popUpColor

HTML-color

The shadow color of the pop up blablabla...

#333333

->ADMINFO

Property

Datatype

Description

Default

cc_email

string

The email address that blablabla...

cc_name

string

The name of blablabla...

cc_return_adr

string

The return address of blablabla...

[servers]

html_emails

boolean

If set, emails are sent in HTML

false

So these are the “objects” and “properties”  you have chosen to offer to your users of the plugin. This reference defines what information makes sense to put into the TypoScript field (semantics) because you will program your application to use this information as needed.

A case story

Now lets imagine that a user inputs this TypoScript configuration in whatever medium you have offered (eg. a textarea field):

colors {
  backgroundColor = red
  fontColor = blue
}
adminInfo {
  cc_email = email@email.com
  cc_name = Copy Name
}
showAll = true
[UserIpRange = 123.456.*.*]
  headerImage = fileadmin/img1.jpg
[ELSE]
  headerImage = fileadmin/img2.jpg
[GLOBAL]
  // Wonder if this works... :-)
wakeMeUp = 7:00

In a syntax highlighted version it would look like this (below) which indicates that there are no syntax errors and everything is fine in that regard:

   0: colors {
   1:   backgroundColor = red
   2:   fontColor = blue
   3: }
   4: adminInfo {
   5:   cc_email = email@email.com
   6:   cc_name = Copy Name
   7: }
   8: showAll = true
   9: 
  10: [UserIpRange = 123.456.*.*]
  11: 
  12:   headerImage = fileadmin/img1.jpg
  13: 
  14: [ELSE]
  15: 
  16:   headerImage = fileadmin/img2.jpg
  17: 
  18: [GLOBAL]
  19: 
  20:   // Wonder if this works... :-)

  21: wakeMeUp = 7:00

(Syntax highlighting of TS (and XML and PHP) can be done with the extension “extdeveval”).

In order to parse this TypoScript we can use the following code provided that the variable $tsString contains the above TypoScript as its value:

   3: require_once(PATH_t3lib.'class.t3lib_tsparser.php');

   4:

   5: $TSparserObject = t3lib_div::makeInstance('t3lib_tsparser');

   6: $TSparserObject->parse($tsString);

   7:

   8: echo '<pre>';

   9: print_r($TSparserObject->setup);

  10: echo '</pre>';

  1. Line 3: The TypoScript parser class is included (most likely already done in both frontend and backend of TYPO3)

  2. Line 5: Create an object of the parser class.

  3. Line 6: Initiates parsing of the TypoScript content in the string $tsString.

  4. Line 8-10: Outputs the parsed result which is located in $TSparserObject->setup

The result of this code is run will be this:

Array
(
    [colors.] => Array
        (
            [backgroundColor] => red
            [fontColor] => blue
        )
    [adminInfo.] => Array
        (
            [cc_email] => email@email.com
            [cc_name] => Copy Name
        )
    [showAll] => true
    [headerImage] => fileadmin/img2.jpg
    [wakeMeUp] => 7:00

)

Now your application could use this information in a manner like this:

echo '<table bgcolor="'.$TSparserObject->setup['colors.']['backgroundColor'].'">

    <tr>

        <td>

            <font color="'.$TSparserObject->setup['colors.']['fontColor'].'">HELLO WORLD!</font>

        </td>

    </tr>

</table>';

As you can see some of the TypoScript properties (or object paths) which are found in the reference tables are implemented here. There is not much mystique about this and in fact this is how all TypoScript is used in its respective contexts; TypoScript is simply configuration values that makes our underlying PHP code act accordingly - parameters, function arguments, as you please; TypoScript is an API to instruct an underlying system.

This also means that now we can begin to meaningfully talk about invalid information in TypoScript - it is obvious that two properties are entered in TypoScript but does not make any sense; “showAll” and “wakeMeUp”. Both properties are not defined in the reference tables and therefore they should neither be implemented in the PHP code of course. However no errors are issued by the parser since the syntax used to define those properties are still right. The only problem is that they are irrelevant; it is like defining a variable in PHP and then never use it! A waste of time - and probably confusing later.

As noted there exists no “semantics-checker” for any TypoScript implementation AFAIK at this writing. It might be interesting and very helpful some day if we had that as well so we could also be warned if we use non-existing properties (which could just be spelling errors).

Implementing Conditions

Now we know how to parse TypoScript and the only thing we need is to implement support for conditions. As stated a few places the evaluation of a condition is external to TypoScript and all you need to do in order to have an external process deal with conditions is to pass an object as the second parameter to the parse-function. This is done in the code listing below:

   1: require_once(PATH_t3lib.'class.t3lib_tsparser.php');

   2:

   3: class myConditions {

   4:     function match($conditionLine)    {

   5:         if ($conditionLine == '[TYPO3 IS GREAT]')    return true; 

   6:     }

   7: }

   8: $matchObj = t3lib_div::makeInstance('myConditions');

   9:

  10: $TSparserObject = t3lib_div::makeInstance('t3lib_tsparser');

  11: $TSparserObject->parse($tsString, $matchObj);

  12:

  13: debug($TSparserObject->setup);

Here goes some notes to this listing:

  1. Line 3-8 defines a very simple class with a function, match(), inside. The function “match()” must exist and take a string as its argument and the match function must also return a boolean value. This function should be programmed to evaluate the condition line according to your specifications.Currently, if a condition line contains the value “[TYPO3 IS GREAT]” then the condition will evaluate to true and parse the subsequent TypoScript.

  2. Line 11: Here the instantiated object, $matchObj, of the “myConditions” class is passed to the parser.

  3. Line 13: Just a little side note: Instead of using PHPs “print_r” function we use the classic debug() function in TYPO3 which prints an array in an HTML table - some of us think this is the nicest way to look into the content of an array (make your own opinion from the screenshot below).

Anyways, let's test the custom condition class from the code listing above. This is done by parsing this TypoScript code:

   0: someOtherTS = 123
   1: 
   2: [TYPO3 IS GREAT]
   3: 
   4: message = Yes
   5: someOtherTS = 987
   6: 
   7: [ELSE]
   8: 
   9: message = No
  10: 
  11: [GLOBAL]
  12: 

  13: someTotallyOtherTS = 456

With this listing we would expect to get the object path “message” set to “Yes” since the condition line “[TYPO3 IS GREAT]” matches the criteria for what will return true. Lets try:

According to this output it worked!

Lets try to alter line 2 to this:

   1: 
   2: [TYPO3 IS great]

   3:

The parsed result is now:

As you can see the value of “message” is now “No” since the condition would return false; The string “[TYPO3 IS great]” is obviously not the same as “[TYPO3 IS GREAT]”! The value of “someOtherTS” was also changed to “123” which was the value set before the condition and since the condition was not true the overriding of that former value did not happen like in the first case.

A realistic example

Most likely you don't want to evaluate conditions based on their bare string value. More likely you want to set up rules for a syntax and then parse the condition string. One example could be this modified condition class which will implement support for the condition seen in the TypoScript listings in the former section, “[UserIpRange = 123.456.*.*]”:

   1: class myConditions {

   2:     function match($conditionLine)    {

   3:             // Getting the value inside of the square brackets:

   4:         $insideSqrBrackets = trim(ereg_replace('\]$','',substr($conditionLine,1)));

   5:         

   6:             // Splitting value into a key and value based on the "=" sign

   7:         list($key,$value) = explode('=',$insideSqrBrackets,2);

   8:         

   9:         switch(trim($key))    {

  10:             case 'UserIpRange':

  11:                 return t3lib_div::cmpIP(t3lib_div::getIndpEnv('REMOTE_ADDR'), trim($value)) ? TRUE : FALSE;

  12:             break;

  13:             case 'Browser':

  14:                 return $GLOBALS['CLIENT']['BROWSER']==trim($value);

  15:             break;

  16:         }

  17:     }

  18: }

This class works in this way:

  1. Line 4: The square brackets in the start (and possibly end as well) of the condition line is removed.

  2. Line 7: The condition line without square brackets is exploded into a key and a value separated by the “=” sign; we are trying to implement the concept of evaluating a data source to a value.

  3. Line 9-16: This switch construct will allow the “key” to be either “UserIpRange” or “Browser”  (the datasource pointer) and the value after the equal sign is of course interpreted accordingly.

Lets try and parse the TypoScript listing from the former section:

   0: colors {
   1:   backgroundColor = red
   2:   fontColor = blue
   3: }
   4: adminInfo {
   5:   cc_email = email@email.com
   6:   cc_name = Copy Name
   7: }
   8: showAll = true
   9:  
  10: [UserIpRange = 123.456.*.*]
  11: 
  12:   headerImage = fileadmin/img1.jpg
  13: 
  14: [ELSE]
  15: 
  16:   headerImage = fileadmin/img2.jpg
  17: 
  18: [GLOBAL]
  19: 
  20:   // Wonder if this works... :-)

  21: wakeMeUp = 7:00

The result of parsing this will be an array like this:

As you can see the “headerImage” property value stems from the [ELSE] condition section and thus the “[UserIpRange = 123.456.*.*]” must still have evaluated to false - which is actually no wonder since nobody can have the IP address range “123.456.*.*” !

Lets change line 10 of the TypoScript to this:

   9:  
  10: [UserIpRange = 192.168.*.*]

  11:

Since I'm currently on an internal network with an IP number which falls into this space the condition should now evaluate to TRUE when the TypoScript is parsed:

... and in fact it does!

Implementing Conditions II

What about implementing the combination of conditions? For instance in the context of TypoScript Templates you can place  more “conditions” in the same (real) condition:

[browser = netscape][browser = opera]

someTypoScript = 123

[GLOBAL]

They are evaluated by OR-ing the result of each sub-condition (done in the class t3lib_matchCondition). We could implement something alike and maybe even better. For instance we could implement a syntax like this:

[ CON 1 ] && [ CON 2 ] || [ CON 3 ]

This will be read like “Returns true if condition 1 and condition 2 are true OR if condition 3 is true”. In other words we implement the ability to AND and OR conditions together.

The implementation goes as follows:

   1: class myConditions {

   2:     

   3:     /**

   4:      * Splits the input condition line into AND and OR parts

   5:      * which are separately evaluated and logically combined to the final output.

   6:      */

   7:     function match($conditionLine)    {

   8:             // Getting the value from inside of the wrapping

   9:             // square brackets of the condition line:

  10:         $insideSqrBrackets = trim(ereg_replace('\]$','',substr($conditionLine,1)));

  11:         

  12:             // The "weak" operator, OR, takes precedence:

  13:         $ORparts = split('\][[:space:]]*\|\|[[:space:]]*\[',$insideSqrBrackets);

  14:         foreach($ORparts as $andString)    {

  15:             $resBool = FALSE;

  16:                 

  17:                 // Splits by the "&&" and operator:

  18:             $ANDparts = split('\][[:space:]]*\&\&[[:space:]]*\[',$andString);

  19:             foreach($ANDparts as $condStr)    {

  20:                 $resBool = $this->evalConditionStr($condStr) ? TRUE : FALSE;

  21:                 if (!$resBool)    break;

  22:             }

  23:             

  24:             if ($resBool)    break;

  25:         }

  26:         return $resBool;

  27:     }

  28:     

  29:     /**

  30:      * Evaluates the inner part of the conditions.

  31:      */

  32:     function evalConditionStr($condStr)    {

  33:             // Splitting value into a key and value based on the "=" sign

  34:         list($key,$value) = explode('=',$condStr,2);

  35:         

  36:         switch(trim($key))    {

  37:             case 'UserIpRange':

  38:                 return t3lib_div::cmpIP(t3lib_div::getIndpEnv('REMOTE_ADDR'), trim($value)) ? TRUE : FALSE;

  39:             break;

  40:             case 'Browser':

  41:                 return $GLOBALS['CLIENT']['BROWSER']==trim($value);

  42:             break;

  43:         }

  44:     }

  45: }

With this implementation I can make a condition line like this:

   9:  
  10: [UserIpRange = 192.168.*.*] && [Browser = msie]
  11: 
  12:   headerImage = fileadmin/img1.jpg

  13:

So if I'm in the right IP range AND has the right browser the value of “headerImage” will be “fileadmin/img1.jpg”

If we modify the TypoScript as follows the same condition applies but if the browser is Netscape then the condition will evaluated to true regardless of the IP range:

   9:  
  10: [UserIpRange = 192.168.*.*] && [Browser = msie] || [Browser = netscape]
  11: 

  12:   headerImage = fileadmin/img1.jpg

This is because the conditions are read like the parenthesis levels show:

(  “UserIpRange = 192.168.*.*”  AND  “Browser = msie”  )  OR “Browser = netscape”

The order of the “||” and “&&” operators may be a problem now. For instance:

   9:  
  10: [UserIpRange = 192.168.*.*] || [UserIpRange = 212.237.*.*] && [Browser = msie]
  11: 

  12:   headerImage = fileadmin/img1.jpg

Imagine this condition; I would like it to read as “If User IP Range is either #1 or #2 provided that the browser is MSIE in any case!”. But right now it will be true if the User IP range is 192.168.... OR if either the range is 212.... and the browser is MSIE.

Formally, this is what I want:

(  “UserIpRange = 192.168.*.*”  OR “UserIpRange = 212.237.*.*”  )  AND   “Browser = msie”

My solution will be to implement a second way of OR'ing conditions together - by simply implying an OR between two “condition sections” if no operator is there. Thus the line above could be implemented as follows:

   9:  
  10: [UserIpRange = 192.168.*.*][UserIpRange = 212.237.*.*] && [Browser = msie]
  11: 

  12:   headerImage = fileadmin/img1.jpg

Line 10 will be understood in this way:

[UserIpRange = 192.168.*.*](implied OR here!)[UserIpRange = 212.237.*.*] && [Browser = msie]

The function match() of the condition class will have to be modified as follows:

   1:     /**

   2:      * Splits the input condition line into AND and OR parts

   3:      * which are separately evaluated and logically combined to the final output.

   4:      */

   5:     function match($conditionLine)    {

   6:             // Getting the value from inside of the wrapping

   7:             // square brackets of the condition line:

   8:         $insideSqrBrackets = trim(ereg_replace('\]$','',substr($conditionLine,1)));

   9:         

  10:             // The "weak" operator, OR, takes precedence:

  11:         $ORparts = split('\][[:space:]]*\|\|[[:space:]]*\[',$insideSqrBrackets);

  12:         foreach($ORparts as $andString)    {

  13:             $resBool = FALSE;

  14:                 

  15:                 // Splits by the "&&" and operator:

  16:             $ANDparts = split('\][[:space:]]*\&\&[[:space:]]*\[',$andString);

  17:             foreach($ANDparts as $subOrStr)    {

  18:             

  19:                     // Split by no operator between ] and [ (sub-OR)

  20:                 $subORparts = split('\][[:space:]]*\[',$subOrStr);

  21:                 $resBool = FALSE;

  22:                 foreach($subORparts as $condStr)    {

  23:                     if ($this->evalConditionStr($condStr))    {

  24:                         $resBool = TRUE;

  25:                         break;

  26:                     }

  27:                 }            

  28:

  29:                 if (!$resBool)    break;

  30:             }

  31:             

  32:             if ($resBool)    break;

  33:         }

  34:         return $resBool;

  35:     }

That's it.

In fact this function might be implemented for TypoScript Templates some day. Currently they only offer the OR'ing of condition-subparts, but this parsing above could probably be applied directly. Who knows, maybe it has already been done when you are reading this document...

Addendum to the reference for our application

Remember in the previous sections? We defined three tables with properties that could be used in TypoScript in the context of our case-story application. To that reference we should now add a section with conditions which defines the following:

#1: Line syntax:

A condition is splitted into smaller parts which are logically AND'ed or OR'ed together. Each sub-part of the condition line is separated by “] (Operator) [“ where operator can be “&&” (AND) , “||” (OR) or nothing at all (also meaning OR “below” AND in order).

The format of the condition line therefore is:

[ COND1 ]  ||  [ COND2 ] && [ COND3 ]  [ COND4 ]  ....etc

where the operators has precedence as indicated by these illustrative parenthesis:

[ COND1 ]  ||  ( [ COND2 ] &&  ( [ COND3 ]  [ COND4 ] ) )

(Notice: Between COND3 and COND4 the blank space is implicitly an OR)

#2: Subpart syntax:

For each subpart (for example “[ COND 1 ]”) the content is evaluated as follows:

[ KEY = VALUE ]

where KEY denotes a type of condition from the table below:

KEY

Description

Example

UserIpRange

Returns true if the clients remote IP address matches the pattern given as value.

The value is matched against REMOTE_ADDR by the function t3lib_div::cmpIP() which you can consult for details on the syntax.

[UserIpRange = 192.168.*.*]

Browser

Returns true if the client browser matches one of the keywords below:

Value you can use:

konqu = Konqueror

opera = Opera

msie = Microsoft Internet Explorer

net = Netscape (or any other)

Values are evaluated against the output of the function t3lib_div::clientInfo() which can be consulted for details on the values for browsers.

[Browser = msie]



TYPO3 Core API

TSRef

TYPO3 Coding Guidelines