Module Development

  • Written by Matthias Mullie on Tuesday 26 July 2011

Please understand that this tutorial is very much W.I.P.!
If you feel you could help us improve/complete this tutorial, please do so in the comments.

Since this tutorial is still W.I.P., it may contain inaccuracies or deprecated functionality. Please point them out if you'd happen to notice such inaccuracies.

1. Foreword

In these series of articles we'll try to explain how Fork CMS works and how you can write your own modules. To help us illustrate certain aspects, we'll be using a mini-blog module. This module is a simplified version of the default blog module. You should start by downloading this mini-blog module.

@todo: add download link once the code has been completed

2. Installing Fork CMS

In these series of articles we are going to work on a local server before moving our work to an online server environment. Before downloading, check if your server (both local and online) meets the minimum requirements for Fork CMS. If it does, create a MySQL database and remember the credentials. Next, download the lastest release and unzip the folder.

Point your localhost (e.g. myforksite.local) to the path where you installed Fork. Then, visit http://myforksite.local/install) to start the installation process.

Please note that Fork can not be installed in subdirectories!

3. Applications

Fork CMS exists out of three applications. The first, perhaps the most important one, is the backend. This is where the content of the website will be created or modified.

3.1 Backend

From a user's perspective, this part of a Fork CMS website can be found at http://myforksite.local/private. It's here where you login with the username and password you entered during the installation.

In the filesystem, everything concerning the backend is located in the /backend folder.

3.2 Frontend

The frontend is where the actual website is located. From a user's perspective the location is of course http://myforksite.local. The files for the webdeveloper however are located in /frontend folder.

3.3 API

A third application, often not used, is the API or Application Programming Interface. With this application you can make your website/application accessible by external websites, applications, ... We'll discuss this in Chapter 22: API.

4. Library

All three Applications make extensive use of the Spoon Library. If you've never worked with Spoon it might be a good idea to browse through the Spoon-documentation, although the Spoon code we'll discuss is pretty self explanatory.

You'll find Spoon in the Library Folder. In this folder, all files are stored which are used by the three applications. You'll find globals.php here too.

If you're using other library's, f.e. to link your website with facebook, twitter, picasa,... it might be a good idea to save them here, in the library/external folder.

5. Routing

One of the strong points of Fork is it's ability to maintain very readable URL's. Not only is this userfriendly, it's very interesting from an SEO point of view too.

5.1 Existing file or folder

When any URL is accessed on your domain, the .htaccess file of Fork CMS is consulted. The most important thing it does is checking if the URL which was opened, is a physical location (file or folder). If this is the case, that location is opened. If the requested url is not an existing file or folder, the routing begins, follow the rules below.

5.2 Language

If the URL's isn't a physical location, a rewrite will occur to /index.php which includes /routing.php. This file makes sure that the correct page is opened.

First, it checks if your website is multilingual, in which case a language indicator will be the first slug in the url. This is something you had to specify while installing Fork CMS.

Multilanguage

If you made a mistake while installing Fork as a multilingual site, you can simply change this by altering the SITE_MULTILANGUAGE -global in the globals.php file.
Caution
: this will alter the generated urls, so pages indexed by search engines may result in a 404.

Active languages

To handle the management of content of different languages, you can enable/disable languages for the frontend. This can be done on the Settings page in Fork CMS.

If your site is multilingual the, first part of the URL is interpreted as a language abbreviation:

  • http://myforksite.local/en/mini-blog/detail/fork-cms-ftw

If this is an active language, that language is chosen.

Zerolanguage

If your site is multilingual but the the first element is not evaluated as a valid language (e.g. myforksite.local or myforksite.local/mini-blog), Fork CMS tries to determine the language based upon the following checks:

  • If a session that was saved, the language for that session will be selected
  • If a cookie that was saved the last time the user visited the site, the language for that cookie will be selected
  • If the user's browser language matches an active language, that language will be selected
  • If above checks don't return an active language, the default language (defined in the backend) will be selected.

5.3 Page

After determining the language, /routing.php searches in a pages-cachefile (which is saved per language: e.g. cache/navigation/navigation_en.php) generated by Fork CMS for the url-part of the matching a page.

In our case, a search will start for mini-blog/detail/pigs-ftw. If this string is not found, the last slug of the url is omitted (mini-blog/detail/pigs-ftw becomes mini-blog/detail) and the cache file is searched again. This happens over and over until a page is found. If no page is found, the request will return the 404 page.

Nesting of pages

When a page is located in a nested structure, this will be visible in the URL. E.g. in the standard installation of Fork CMS, the “Location” page is located beneath “About-us”. The URL for this page looks like:

  • http://myforksite.local/en/about-us/location

It's the part in bold that is saved in the cache file.

5.4 Action

If a page is found (mini-blog), the next part of the URL (if any) will be interpreted as an action, (in the case of mini-blog/detail/pigs-ftw, the action will be detail). If this is an existing action in the module linked to the page, that action will be executed. If it doesn't exist, the default action defined in the config-file will be executed.

The action-part in the url (in this case: detail) will map to a matching action-translation. detail is one of the default actions already, but if you're adding a new or uncommon action, don't forget to create a translation for it. Internally, the action-part in the url will be searched for in the translations, and it's reference code will be used to route to the correct action, this way it's possible to even translate that part in the url when dealing with multiple languages.

Example: http://myforksite.local/en/mini-blog/detail/pigs-ftp
Fork CMS will browse through all action-translations for this language (in this case: en) until it finds one that's been translated to detail. Here, it'll find the action translation with reference code Detail, which will cause your detail.php action to be launched.

5.5 Parameters

When an action is selected, the remaining part of the URL is interpreted as a list of parameters. In our case we have only one parameter: pigs-ftw. What will happen if the parameter(s) is valid or not depends completely on how the action is developed.

6. Modeling your module

6.1 What will the module do?

The easiest way to start developing a module is to make your mind up about what the module is supposed to be doing. You'll completely have to separate this for frontend and backend. In our example we'll talk about the frontend. The workflow for the backend would be identical, but we would do completely different things (delete, edit, add, ...) as we'll see later on.
In the frontend of our mini-blog there will be 4 different functionalities:

  • View a list of our blogposts (index)
  • View the details of one blogpost (detail)
  • Get a list of the most recent posts (recent_posts)
  • Mark a blogpost as frikkin' awesome (this_is_awesome)

For every functionality you'll create a .php file.

6.2 How will these functionalities work?

After you have decided what your module has to do, you have to decide when these will be executed:

  • on pageload (see "how")
  • ajax: when the page is already loaded

And how they will be presented

  • action: the most important part of a page
  • widget: just a smaller part of the page

Depending on these choices, you'll place your .php files in either the actions, ajax or widget folder.

As discussed earlier, actions are url-aware. When a module is linked to a page, the exact action that will be executed depends upon the url.
Given our previous example:

  • http://myforksite.local/en/mini-blog has no action defined and will load the default action (in this case the list of our examples: index.php)
  • http://myforksite.local/en/mini-blog/detail/fork-cms-ftw defines an action detail, so detail.php will be loaded. It is up to detail.php to determine what to do with the remaining fork-cms-ftw in the url.

For this reason, only 1 module can be linked to a page whereas several widgets (which are url-independent) can be linked to 1 page. Usually, widgets are lesser important parts that exist mainly to inform or attract users to actions.
In a blog module, the blogpost-overview and blogposts themselves obviously should be actions, whereas a widget could be a tiny part displaying the titles of the 3 latest blogposts. Such widget could be added to any page in the sidebar, the footer, ...

The this_is_awesome action will be activated by clicking a button. This will add 1 to the awesomeness column of the record of a specific blog post. We will then hide that button, and update a counter on the page. Because the complete page will not to be reloaded, this is a typical AJAX-action.

Combinations

Sometimes it's easier to use one .php file for a couple of things you want to do. Imagine we would like to add a “comment” feature to the mini blog. The best way to do this is to add a comment form to the detail page (= add it to the comment action.) When the form is submitted, we reload the page and check if the form was submitted. If so, we add the comment and reload the detail page. This way of working is described in Chapter 11: Forms.

Note: You could of course be adding comments with Ajax too.

6.3 Defining your module in the database

Before you can use the module, some records needs to be added to the database. Typically this is done by the installer you'll write at the end of this manual, but here's an overview already, so you can start developing your module.

6.3.1 modules

In this table, you add the module name. Don't use camelCasing when giving the name, but the exact representation of the folder containing your module's code. mini_blog is what we add.

6.3.2 modules_extras

The records in this table describe the different blocks and widgets we can add to our pages.
If the different actions of your module will be using the same page layout you can add a record which in our case looks something like:

Column Value Additional information
module mini_blog The module name
type block Either block or widget, depending on the choices made above
label MiniBlog The label used to display the name of this page extra, this will call lblMiniBlog - more on that in Chapter 10: Translations
action NULL

Insert the exact name of the action (e.g. index, detail) if you only want this exact action to be shown when this is linked to a page (generally not advised, as every action will then have to be linked to another page)
Insert NULL to let Fork CMS define the action based upon the url (advised, this way it is possible to make all actions of 1 module accessible through 1 page where this extra is linked to)

Should you want to add one specific action as a separate block - perhaps because the page layout (sidebar, footer, ...) is completely different from other actions so another template and thus another page must be used - you add the same record, but with the action field filled in, e.g. detail.

For our widget we need to add a record like this:

Column Value
module mini_blog
type widget
label RecentPosts
action recent_posts

6.3.3 groups_rights_modules

By default, only one group is installed (1: admin). If you want to give this group access to the module in the backend, you need to add a record for the module in this table.

Note: the user that installed Fork CMS is considered the God-user. This user always has all permissions. These rights are used for other users to determine if they have access to modules and actions in the backend. Rather than setting these rights directly into the database, we strongly recommend using the built-in Group Permissions tool (located at http://myforksite.local/private/en/groups/index) to set the correct privileges for your users, otherwise they will not be able to manage the module.

6.3.4 groups_rights_actions

When you've given a group the rights for the module, you will need to add a record for each backend-action that the group must be able to use. In our case, we'll have an add, edit, delete and index action.

You'll see a field level in this table which you always must fill in with 7 (inspired by read, write & excecute in the UNIX filesystem)

Note: as described above, we strongly recommend using the built-in Group Permissions tool over manually adding the permissions in the database.

6.4 The visible parts of the module

The action- & widget-files are controllers that will handle and manipulate the data, but do not contain any visual representation. For every action that has a visual representation we need to create a template.

As you might have already noticed, all the actions except the this_is_awesome action have a template-file (.tpl). They can all be found in the layout/templates folder, except the template for the recent_posts widget. This can be found in the layout/widgets folder.

Templates files are regular .html-files but contain placeholders for the data the actionfiles will fetch from the database. More about these templates we'll see in Chapter 9: Templates.

When browsing (e.g. to http://myforksite.local/en/mini-blog/detail/fork-cms-ftw) the same routing conventions will be used to select the correct template. The template should therefor always have the same name as the action/widget file, followed by ".tpl"
If for any reason, you need to use a template that does not follow these naming conventions, it is possible to override this behaviour in the called-upon action/widget.

6.5 Config

In the root folder of every module, there will be a config.php. This is the starting point when the module is executed. Here you define which action is the default action (in case of no action is provided in the URL as we saw in the routing chapter), and which actions should be disabled.

Why write disabled actions?

You can disable actions in config.php, but why would you write them in the first place?

When you are writing your own modules, perhaps one day you'll find yourself in the situation where you can re-use an earlier written module (or use a module shared by someone else) but you don't need all it's actions. In that case, in the config file, you can disable these actions.

Another plausible scenario is when you create actions that do bulk operations on the database, like populating the database with dummy content or removing all existing entries. You do not want the actions to accidently be called upon once the website goes live, but neither do you want to completely remove them (because you never know some day they might come in handy.)

6.6 Backend navigation

For the frontend, you alter the complete navigation by dragging the page to the right places on the “pages”-page in the backend.
For the backend however, when you write a module, you need to add the backend navigation that is used to edit the module in the /backend/cache/navigation/navigation.php. Actually, inserting the module into the backend navigation will usually be handled in the installer, which we'll get to in Chapter 24: Creating an installer.

In this file, you'll find a multidimensional array representing the complete backend navigation. For more information check out the file itself.

For the mini-blog module, you'll want to add the following array to the element “children” of the array with the label “Modules”, after which the pages will become visible on the “Modules” page.

array(
    'url' => 'mini_blog/index',
    'label' => 'MiniBlog',
    'children' => array(
        array(
            'url' => 'mini_blog/index',
            'label' => 'Articles',
            'selected_for' => array(
                'mini_blog/add',
                'mini_blog/edit'
            )
        )
    )
)

7 Writing your module

7.1 Autoloading

Before we start writing our first lines of code, a word about the naming of your files, classes, labels, ... If you name all your files well, you'll never have to include, require or embed a single file, Fork CMS will do this for you.

7.1.1 camelCasing

All your classes, variables, id's... in your php, js and css-files, should be named using camelCasing. Names will exists only out of letters and numbers (there are no spaces) but every new word starts with an uppercase letter. Some examples:

  • new message becomes newMessage
  • a great class becomes aGreatClass
  • variable becomes variable
  • mini blog becomes miniBlog

Your files and tables & columns in the database however, should never be written in camelCasing, but always in lowercase and an underscore instead of a space. The examples above become:

  • new message becomes new_message
  • a great class becomes a_great_class
  • variable becomes variable
  • mini blog becomes mini_blog

7.1.2 Javascript

Not only will your .php files be autoloaded, you can autoload js-files too. There are two possibilities.

You name your file like the module, e.g. mini_blog.js and put it in the js-folder in your module folder. This way, the js-file will be included on every page of the module. This is what we're doing for our mini-blog module because we need the same javascript code on the detail and the index page.

Should a certain peace of js-code only be used on one action, then you give it the name of that action, e.g. detail.js.

7.1.3 Templates

As you have have seen in our example, the module folder contains a layout folder which contains the templates and widgets to display the pages. These templates can be considered a default. When a new theme is applied to the website, the template for every module can be overwritten to better match the theme's style.

To avoid altering files in the module folder (so you can re-use it later without having to think about altered code), you can create your own theme which will have a folder in the themes folder: /frontend/themes/the_a_theme/modules/. Here you put a folder with the module name (mini_blog) which contains the templates, widgets, css, ... folders, which in turn contain the custom files.

When Fork CMS opens a webpage, it first searches for the requested template in the /frontend/themes/<selected_theme_in_fork_cms>/modules/modulename/layout/templates folder. If a template with the correct name (e.g. detail.tpl) is not found, Fork CMS falls back to the template in the default folder, frontend/modules/modulename/layout/templates.

Backend templates

In contrast to the frontend, the templates for the backend actions are always located in “backend/modules/modulename/layout/templates”. Themes can only be applied to the frontend, so the overwrite rules don't apply to the backend.

7.2 Model

As you perhaps have noticed, the only PHP-file we didn't cover in the previous chapter is the model.php file, perhaps the most important one.

This file contains all the generic functions, most of the time functions that make a connection to the database.

A rule of thumb should be that every line of (parametrized!) SQL you'll write, has to be written in a model.php file.

Beneath you'll find some lines of the model.php file of the mini blog module. We'll use this example to illustrate some conventions.

class FrontendMiniBlogModel implements FrontendTagsInterface
{
    ...

    /**
    * Fetch one article based upon it's meta url
    *
    * @return array
    * @param string $URL The URL for the item.
    */
    public static function get($URL)
    {
        return (array) FrontendModel::getDB()->getRecord('SELECT i.id, i.language, i.title, i.introduction, i.text,
            UNIX_TIMESTAMP(i.edited) AS edited, i.user_id,
            m.keywords AS meta_keywords, m.keywords_overwrite AS meta_keywords_overwrite,
            m.description AS meta_description, m.description_overwrite AS meta_description_overwrite,
            m.title AS meta_title, m.title_overwrite AS meta_title_overwrite, m.url
            FROM mini_blog AS i
            INNER JOIN meta AS m ON i.meta_id = m.id
            WHERE i.language = ? AND i.publish = ? AND m.url = ?
            LIMIT 1',
            array(FRONTEND_LANGUAGE, 'Y', (string) $URL));
    }

    ... class continues for another 200 lines
}

First we encounter the naming of the class. We can split this in three parts. Mind that classnames always begin with an uppercase and the rest follows the general in camelCasing.

  1. The application, because we'll have a Backend model too (Frontend)
  2. The module (MiniBlog)
  3. The action/name of the file (Model)

It can be a good idea to write your model.php first. The first thing you have to do is figure out what exactly every action has to be able to do.

  • detail
    • get all the details of one article
    • get the previous and next article (for navigation)
  • index
    • get a list of items, ordered by date, grouped by a given number and offset
    • get the total amount of articles (for navigation)
  • recent_posts
    • get a list of items, ordered by date, grouped by a given number
  • this_is_awesome
    • check if an article exists
    • add 1 to the number of reports of the article

As you are a programmer you'll probably have seen that we want to fetch a list of items twice, in almost the same way. Instead of writing this code twice for both actions, we write it in model.php where it's shared between the actions (and even other modules.)

If you check the code in model.php, you'll see we have some other methods too concerning implementing tags and searching, ... we'll discuss these later.

8 Blocks and widgets

8.1 Adding pages

@todo: Explain how pages are added to the website. This covers adding pages, templates and assigning blocks.

8.2 What's the difference?

Blocks and widgets are the visible parts of your websites. The blocks, you'll find in the actions folder, the widgets you'll find in the widgets folder. Their operation is exactly the same with two differences:

  • There can only be one “block” on a page.
  • Because of this, each block has an URL (that can be fetched using the function getUrlForBlock)

This block can be a module, with every action included, or just one action of a module.

As long as the selected template allows it, there can be as many widgets as you want on a page. The structure of a widget is (most of the time) less complicated than a block because they are used merely for displaying data.

8.3 Structure

When you check out other existing Fork modules, you will see that most actions use the same structure, using the same method names. Beneath you'll find the complete code of the detail-action of our mini blog.

class FrontendMiniBlogDetail extends FrontendBaseBlock
{

Again the classname needs the be exact ApplicationModuleAction, in our case FrontendMiniBlogDetail. Because our action is a Block, it extends FrontendBaseBlock. This class takes care of everything concerning URL-handling, breadcrumbs, ...

/** 
* The blogpost
*
* @vararray
*/
private $record;

Then we define our (private) variables. In our case we'll use an array to save the record with the article we will be viewing.

public function execute()
{
// call the parent
parent::execute();

// hide contenTitle, in the template the title is wrapped with an inverse-option
$this->tpl->assign('hideContentTitle', true);

// load template
$this->loadTemplate();

// load the data
$this->getData();

// parse
$this->parse();
}

The execute function is always present and is called by Fork CMS when opening any action. As you can see, the execute method of FrontBaseBlock is called too. This makes sure that the js-files and css-files are autoloaded.

The line starting with "$this->tpl->assign(" ... assigns a variable to the template we'll be using to display the action.

loadTemplate (also defined in FrontBaseBlock) loads the template file in which we parse the data we'll be loading in our self defined method getData.

 /*
* Load the data
*
* @return void
*/
private function getData()
{
// if no parameter was passed we redirect to the 404-page
if($this->URL->getParameter(1) === null) $this->redirect(FrontendNavigation::getURL(404));

// get the record, or at least try it
$this->record = FrontendMiniBlogModel::get($this->URL->getParameter(1));

// if the record is empty it is an invalid one, so redirect to the 404-page
if(empty($this->record)) $this->redirect(FrontendNavigation::getURL(404));

// add some extra info to the record
$this->record['full_url'] = FrontendNavigation::getURLForBlock('mini_blog' , 'detail') . '/' . $this->record['url'];
$this->record['tags'] = FrontendTagsModel::getForItem('mini_blog' , $this->record['id']);
}

The getData function first checks if the item given in the URL exists and adds some extra data which will be used in the template, or redirects to a 404-page if it doesn't. (The 404 page is installed by default when installing Fork).

If an article was found, the data we fetched is parsed into the template-file.

private function parse()
{
$this->breadcrumb->addElement($this->record['title']);

$this->header->setPageTitle($this->record['title']);
$this->header->setMetaDescription($this->record['meta_description'] , ($this->record['meta_description_overwrite'] == 'Y'));
$this->header->setMetaKeywords($this->record['meta_keywords'] , ($this->record['meta_keywords_overwrite'] == 'Y'));

$this->tpl->assign('item', $this->record);
$this->tpl->assign('navigation' , FrontendMiniBlogModel::getNavigation($this->record['id']));
}
}

As you can see, it's fairly easy to add an item to Fork's breadcrumb object and to add the meta-data to the <head> of the page. We discuss both objects later on.

Note: All documentation below has not been verified recently and is more likely to contain inaccuracies.

9 Templates

@todo: Explain how to create your own template.

9.1 Variables

To make our different pages visible on our website we need to write a template file for every action. As said before, a template file is a html-file containing placeholders for the data that we will parse into that template-file.

Let's first have look at the detail-page, in the default Fork CMS-theme, in the browser:

The code that was used in the .tpl-file you find below. As you can see it's regular html-code, but with a lot of data between { and }. We'll discuss the different types of placeholders one by one.

<div id="blogDetail">
<article class="mod article">
<div class="inner">
<header class="hd">
<h1>{$item.title}</h1>
<ul>
<li>{$msgWrittenBy|ucfirst|sprintf:{$item.user_id| usersetting:'nickname'}} {$lblOn} {$item.edited|date:{$dateFormatLong}:{$LANGUAGE}}</li>
</ul>
</header>
<div class="bd content">
{$item.introduction}
</div>
<div class="bd content">
{$item.text}
</div>
<div class="awesome" id="awesome{$item.id}">
<span class="counter">{$item.awesomeness}</span>
{$lblPeopleThinkThisPostIsAwesome}
<span class="bar">|</span>
<a class="add" rel="{$item.id}">{$lblIThinkThisIsAwesome}</a>
<span style="display:none;" class="added">{$lblIThinkThisIsAwesome}</span>
</div>
<footer class="ft">
<ul class="pageNavigation">
{option:navigation.previous}
<li class="previousLink">
<a href="{$navigation.previous.url}" rel="prev">{$lblPreviousArticle|ucfirst}: {$navigation.previous.title}</a>
</li>
{/option:navigation.previous}
{option:navigation.next}
<li class="nextLink">
<a href="{$navigation.next.url}" rel="next">{$lblNextArticle|ucfirst}: {$navigation.next.title}</a>
</li>
{/option:navigation.next}
</ul>
</footer>
</div>
</article>
</div>

When we discussed the detail.php file we came across the following line:

 $this->tpl->assign('item', $this->record); 

With this code, the array in $this->record, containing our article, is parsed into the template as the array item. The syntax for reading elements from an array in a template is the dot-syntax, preceding the variable name with an $. On the sixth line your see {$item.title}. This does nothing more than echoing the value in the field 'title' of our array 'item'.

In some cases, you'll have array's in array's, in array's, in ... you'll write them next to each other as in our example: {$navigation.previous.title}

Plain simple vars

Not everything needs to be an array. A normal variable is possible too:
$articleTitle = 'Finding Little Nemo in Slumberland';
$this->tpl->assign('title', $articleTitle);

You can echo it in your template like this:
{$title}

9.2 Locale

When you're developing a module you'd better don't write plain English, Dutch, French, ... in your templates. Instead you use locale which will be replaced by the right translation. You see a couple of locale in our example:

  • {$msgWrittenBy}
  • {$lblOn}
  • {$lblPeopleThinkThisPostIsAwesome}

Everything what applies to normal data applies to labels as well. They stand between { and }, are preceded by $ and can be used in combination with modifiers (see next chapter). There are two differences. The first is that you don't have to parse them in the template yourself, this happens automatically.

The second is that labels always begin with lbl, msg, act or err. More about this, you'll find in the next chapter.

9.3 Modifiers

Because plain simple translations or only echoing data is not always what we need, you can add modifiers to an echo of data or a label. Take this example:

{$item.edited|date:{$dateFormatLong}:{$LANGUAGE}}

The variable {$item.edited}, echoed without modifiers would look something like:

1303481726

... a UNIX-timestamp.

We put a pipe next to the variable name, followed by the modifier name and next you'll find two arguments, separated by colons. {$dateFormatLong} and {$LANGUAGE}, two other variables automatically parsed into the template by Fork. The result of this expression becomes

Friday 22 April 2011

It's possible to send the result of one modifier to another modifier, just add a pipe and write the next modifier.

modifiers

There a lots of modifiers available, some with arguments (f.e. sprintf, substring, ...), others without, (ucfirst, nl2br, ...). Also check our cheatsheet.

9.4 Commenting

Templates have other possibilities too. To illustrate these we'll use the template of the recents_posts widget.

As you see on the first 4 lines, we added some comments. When you're working with different people on a website it might be a good idea to provide on overview of which variables are parsed into the template.

{*
variables that are available:
- {$widgetMiniBlogRecentPosts}
*}
<section id="blogRecentCommentsWidget" class="mod">
<div class="inner">
<header class="hd">
<h3>{$lblRecentArticles|ucfirst}</h3>
</header>
<div class="bd content">
{option:widgetMiniBlogRecentPosts}
<ul>
{iteration:widgetMiniBlogRecentPosts}
<li>
<a href="{$widgetMiniBlogRecentPosts.full_url}">{$widgetMiniBlogRecentPosts.title}</a>
{$msgWrittenBy|ucfirst|sprintf: {$widgetMiniBlogRecentPosts.user_id|usersetting:'nickname'}} {$lblOn}
{$widgetMiniBlogRecentPosts.edited|date:{$dateFormatShort}:{$LANGUAGE}}
</li>
{/iteration:widgetMiniBlogRecentPosts}
</ul>
{/option:widgetMiniBlogRecentPosts}
{option:!widgetMiniBlogRecentPosts}
{$msgThereAreNoRecentItemsYet}
{/option:!widgetMiniBlogRecentPosts}
</div>
</div>
</section>

9.5 Options

Templates have two programming techniques. The first are options. They work just the same as an if-statement, except the only thing you can check is: Is a variable set, or not set.

In our example above we check if the variable widgetMiniBlogRecentPosts isset. If so, the code between the options is show. Else, it's not shown!

You add the “Else” yourself by reversing the same option with an exclamation mark. This way you can add some kind of error message.

!isset()

The evaluation of an option doesn't work the same as the “isset” function in php. An option is FALSE when the variable is:
an empty string, 0 (string or integer), NULL, FALSE (boolean), an empty array.

9.6 Iterations

The second programming technique your can use in templates are iterations. With iterations, you can walk all the items of an array. This example prints all the title fields of an array.

{iteration:widgetMiniBlogRecentPosts}
{$widgetMiniBlogRecentPosts.title}
{/iteration:widgetMiniBlogRecentPosts}

There are a lot of other things you should know about iterations, such as nesting iterations, the first- and last-option, cycle, ... You'll find everything about these on the Cheatsheet.

No $

Mind that in neither in options nor iterations, the variable name is preceded by the dollar-sign.

9.7 Includes

Another nice thing about templates is that you can include other templates, just like you do in .php.

E.g. in the backend, you will add the following lines to every action template so all pages start with the same header:

{include:{$BACKEND_CORE_PATH}/layout/templates/head.tpl}
{include:{$BACKEND_CORE_PATH}/layout/templates/structure_start_module.tpl}

The same way, you can add your own pieces of code that perhaps often return on other pages, without it's necessary to create a widget. E.g. a collection of "Share on twitter, facebook, netlog, myspace, ..." buttons.

10 Translations/Locale

Most websites you'll make will be available in different languages. Creating a separate website for each language is a waste of time, that's why Fork CMS has the Locale Module. As we saw in the previous chapter, we'll use placeholders in the templates to display the right text depending on what's the active language. This text has to be added in the database, which you'll do in the backend, under settings, translations.

Just one language?

Even if you're website has one language it still might be a good idea to use the Locale module:

  • You'll never know if a language needs to be added in the future.
  • You can give your customer access to the Locale Module. This way she/he can edit the labels him/herself. Otherwise, for every typo- you'll need to alter the text yourself in the code.

10.1 Types

10.1.1 Labels

Labels are used the provide a short literal translation. E.g.:
BlogCategories => blog categories

10.1.2 Messages

Messages provide translations for longer texts. E.g.:
NoItems => there are no blog posts yet. You can add one by clicking the add button above.

10.1.3 Actions

Actions are keywords that occur in the url (and as such are confined within a smaller set of characters: e.g. a questionmark is a no-go, since in url's it identifies the start of the querystring)

An example of an action would be:
Detail => detail

10.1.4 Errors

When you want to display error messages, use this type of translation. E.g.:
FieldIsRequired => This is a required field.

What's the difference?

Apart from actions, there's no technical difference between the types. We just like to keep things tidy ;)

10.2 Adding translations

As we said before, translations are added/edited in the backend. Here you see a screenshot of the input form:

The reference code is the name of the translation (Add, NoItems, ...). This code is always written in camelCasing with an uppercase first letter. This is because we'll be using a prefix depending on the type of translation when we're calling the translation in the templates.

labels => {$lblNameOfTheTranslation}
messages => {$msgNameOfTheTranslation}
actions => {$actNameOfTheTranslation}
errors => {$errNameOfTheTranslation}

As you see, now it's “real” camelCasing.

The translation is the text that has to be displayed, depending on which language is used when viewing the website.

You'll have to select an Application too, Backend or Frontend. You select backend when you want to add a translation to the backend pages of your module. The translations of backend and frontend are saved in different files.

When you selected Frontend in the Application dropdown, you'll see you can only select core in the “module”-dropdown.

When adding translations for the backend however, you can select all the existing modules. This way, you can add different meanings for one Reference code:

module reference code translation
blog NoItems no blog posts yet
mini_blog NoItems no articles yet
news NoItems no news yet
core NoItems no items yet

When editing a page, Fork CMS searches for a module specific translation, e.g. “no blog posts yet”. When the module specific translation was not supplied, Fork CMS searches for the “NoItems” translation for the “core” module, “no items yet” in our case.

Then, you just need to add the type of translation.

Continue to part 2