This chapter discusses the XTemplate SE (Special Edition) template engine built into the Seditio CMS core, describing its syntax constructs (blocks, conditions, loops, filters), skin file architecture, PHP API for template integration, and customization mechanisms for modules and plugins.
9.1. Introduction and XTemplate SE Concept
The Seditio CMS template engine ensures complete separation of application logic from its visual presentation.
9.1.1. Origin and Purpose
The XTemplate class in Seditio CMS (file system/templates.php) is a deep adaptation of the CoTemplate engine used in the Cotonti project, which in turn inherits and develops the classical XTemplate concept. The primary goals of using the template engine in the system are:
- Development Security: The layout designer works exclusively with HTML markup and template placeholders, without the ability to execute arbitrary PHP code directly in templates.
- Code Cleanliness: Core controllers, modules, and plugins format data in PHP, while the template engine handles rendering exclusively.
- Performance: The template engine compiles source TPL files into an intermediate block structure cached by the
XtplCacheclass. On subsequent requests, the cached template is loaded instantly, bypassing regular expression parsing.
9.1.2. Compilation and Caching Mechanism
On the first template invocation, the core reads the source .tpl file, processes dynamic file inclusion directives, strips BOM characters, and compiles the template into an intermediate block structure cached by XtplCache. On subsequent hits, the cached template is loaded immediately, bypassing regular expression parsing.
9.1.3. Differences Between XTemplate SE and CoTemplate
Despite sharing a codebase with CoTemplate (Cotonti), Seditio's XTemplate SE version features key architectural differences:
- Security (Function Filtering):
- XTemplate SE: Restricts function calls (filters) in templates (via
whitelist/blacklistmodes). By default, only safe helpers (e.g.,trim,date,crop,resize) are allowed. - CoTemplate: Permits designers to call any PHP functions without restrictions, creating RCE risks.
- XTemplate SE: Restricts function calls (filters) in templates (via
- Arithmetic Expressions in Output Tags:
- XTemplate SE: Supports evaluation of simple expressions directly inside variable placeholders:
{SOME_TAG + 5}or{VAR_1 * VAR_2}. - CoTemplate: Restricts arithmetic only to condition tags
<!-- IF ... -->.
- XTemplate SE: Supports evaluation of simple expressions directly inside variable placeholders:
- Compilation Code Cleanliness and Decomposition:
- XTemplate SE: Block parsing logic (
XtplBlock::compile()) is split into clean, compact methods (detectNextConstruct,extractConstruct,compileConstruct). - CoTemplate: Compilation is a monolithic 150-line method with nested loops and regular expressions.
- XTemplate SE: Block parsing logic (
- Optimized RPN Expression Parser (
XtplExpr):- XTemplate SE: Stores operators in the RPN stack as strings.
- CoTemplate: Uses over 20 global constants to encode operators, complicating code and cluttering the global namespace.
- Optimized Disk Caching:
- XTemplate SE: The
XtplCacheclass serializes the compiled template into one file on disk. - CoTemplate: Creates three separate files (
.tpl,.idx,.tags) per template, increasing filesystem I/O load.
- XTemplate SE: The
- Dedicated Debugging (
XtplDebugger):- XTemplate SE: Logging debug info is moved to a separate
XtplDebuggerclass. - CoTemplate: Debugging logic is hardcoded inside
parse()andout()rendering methods.
- XTemplate SE: Logging debug info is moved to a separate
- Global Configuration:
- XTemplate SE: Configuring options is done via a single static entry point
XTemplate::configure(). - CoTemplate: Configured via the
init()method.
- XTemplate SE: Configuring options is done via a single static entry point
9.2. Template Syntax: Blocks, Loops, and Conditions
Seditio templates use the default .tpl extension. The syntax consists of placeholders (tags) and control comments.
9.2.1. Named Blocks
All markup in XTemplate SE is built on a hierarchical block structure. A block is declared using special HTML comments:
<!-- BEGIN: MAIN -->
<div class="wrapper">
<h1>{PAGE_TITLE}</h1>
<!-- BEGIN: SUBBLOCK -->
<p>Subblock additional text: {SUB_INFO}</p>
<!-- END: SUBBLOCK -->
</div>
<!-- END: MAIN -->
- The root (primary) block in a file is almost always the
MAINblock. - Blocks can be nested to any depth.
- In PHP code, the block path relative to the root is used for parsing (e.g.,
MAINorMAIN.SUBBLOCK).
9.2.2. Conditions (IF/ELSE)
Conditions allow controlling element displays depending on passed variables:
<!-- IF {PAGE_AUTHOR} == 'Admin' -->
<span class="badge red">Administrator</span>
<!-- ELSE -->
<span>Author: {PAGE_AUTHOR}</span>
<!-- ENDIF -->
The expression analyzer XtplExpr is used inside the <!-- IF {EXPR} --> tag. The following operations are supported:
- Arithmetic:
+,-,*,/,%. - Comparison:
==,===,!=,!==,<,>,<=,>=. - Logical:
AND(or&&),OR(or||),XOR,!(logical negation). - Seditio-specific:
HAS— checks if an element is in an array (similar to PHP'sin_array()). E.g.:<!-- IF {USER_GROUP_ID} HAS 5 -->...<!-- ENDIF -->.~=— checks substring inclusion (similar tostrpos() !== false). E.g.:<!-- IF {PAGE_TITLE} ~= 'Important' -->...<!-- ENDIF -->.
9.2.3. Loops (FOR)
XTemplate SE supports automatic iteration over arrays:
<ul>
<!-- FOR {VAL} IN {PHP.cfg.menu_links} -->
<li><a href="{VAL.url}">{VAL.title}</a></li>
<!-- ENDFOR -->
</ul>
Obtaining key/value pairs of associative arrays is also supported:
<ul>
<!-- FOR {KEY}, {VAL} IN {PHP.usr.auth_groups} -->
<li>Group ID: {KEY}, Name: {VAL}</li>
<!-- ENDFOR -->
</ul>
When loop tags are used, the engine automatically calculates the array size and renders block contents for each iteration, populating the loop variables.
9.2.4. File Inclusions (FILE)
To assemble pages from components (e.g., including site headers, footers, or widgets), the FILE tag is used:
{FILE "skins/default/header.tpl"}
The file path can be quoted with single or double quotes. Included templates can contain their own blocks, conditions, loops, and tags, which will be compiled into the single template tree.
9.3. Placeholders (Tags) Syntax and Filters
Placeholders are variables inside curly braces replaced with text or HTML data during execution.
9.3.1. Basic Variables and Arrays
- Simple tag:
{PAGE_TITLE}. - Multidimensional array element:
{PAGE_ROW.page_cat}or{PAGE_ROW.page_owner.user_name}.
9.3.2. Function Filters (Callbacks)
Placeholder values can be modified on the fly using filter chains separated by a vertical bar |:
<p>{PAGE_TEXT|strip_tags|trim}</p>
Filters can accept arguments passed in parentheses separated by commas:
<h2>{PAGE_TITLE|strtolower|ucfirst}</h2>
<p>Excerpt: {PAGE_TEXT|strip_tags|substr(0, 150)}</p>
<p>Publication date: {PAGE_DATE|date("d.m.Y H:i", $this)}</p>
[!IMPORTANT]
Function Filtering:
To ensure security, the
XTemplateclass implements a whitelist of approved functions. In templates, only safe functions approved by the core can be called (by default:trim,nl2br,date,htmlspecialchars,strip_tags,substr,sprintf,count,resize,crop, etc.). Attempting to call system execution functions likeeval,system, orfile_get_contentswill result in security errors or blocks.
9.3.3. Global Context (PHP Prefix)
Seditio templates provide direct access to global variables and core arrays via the PHP. prefix:
{PHP.cfg.mainurl}— website root URL.{PHP.usr.name}— username of the current visitor.{PHP.usr.id}— visitor's user ID (0 for guests).{PHP.L.Yes}/{PHP.L.No}— localized language strings.{PHP.out.subtitle}— current page subtitle.
9.4. Skins and Templates Structure
User-defined design themes (skins) are stored in the /skins/ directory. Standard templates of modules and plugins are supplied by default in their respective folders: /modules/{module_name}/tpl/ and /plugins/{plugin_code}/ (or /plugins/{plugin_code}/tpl/).
A skin in Seditio is a set of overriding templates (.tpl), stylesheet files (.css), scripts (.js), and media assets that allow you to completely customize the interface without editing core module and plugin files.
9.4.1. Skin Folder Location
The directory of a specific skin matches the format: /skins/{skin_code}/ (e.g., /skins/default/).
9.4.2. Core and Module Templates
Templates can be stored in the active skin folder (/skins/{skin_code}/) or inside module folders (/modules/{module_name}/tpl/). The core function sed_skinfile() searches for template files, prioritizing active skin templates, falling back to module folder defaults.
Below is a list of default template files grouped by purpose:
System Templates
header.tpl— global site header. Contains HTML headers, meta tags, the{HEADER_STYLESHEET}tag to include CSS files, and the navigation bar.footer.tpl— global site footer. Contains closing tags, counters, developer copyrights, and the{FOOTER_DEVINFO}tag (displays generation time, SQL queries, and RAM usage).index.tpl— layout for the homepage.message.tpl— displays system alerts, success messages, and errors.popup.tpl— template for popup windows (e.g., smilies or BB-codes insert lists).plugin.tpl— default wrapper for standalone plugins lacking custom templates.breadcrumbs.tpl— layouts for breadcrumbs navigation paths.maintenance.tpl— placeholder page for technical maintenance mode.
Page Module (Page)
page.tpl— details view of an individual page.page.add.tpl— page creation form for frontend users.page.edit.tpl— page edit/update form.page.print.tpl— printable version layout.list.tpl— displays the list of pages inside a category.list.group.tpl— page lists grouped by subfolders.admin.page.tpl— administration page lists layout.admin.page.add.tpl/admin.page.edit.tpl— page forms in the admin area.admin.page.manager.tpl— Page Manager tool in the admin area.
Forum Module (Forums)
forums.sections.tpl— main forums page (categories list).forums.topics.tpl— topics list inside a forum section.forums.posts.tpl— posts list and quick reply form inside a topic.forums.newtopic.tpl— new topic creation form.forums.editpost.tpl— post editing form.admin.forums.tpl/admin.forums.structure.tpl— forum sections administration.
Personal File System (PFS)
pfs.tpl— file manager layout (folders, file uploads).pfs.edit.tpl— file/folder properties edit form.pfs.standalone.tpl— popup file manager layout (for editor attachments).pfs.edit.standalone.tpl— popup file properties editor.admin.pfs.tpl— user file system management in the admin area.
Users Module (Users)
users.tpl— general registered users list.users.details.tpl— public details profile card.users.profile.tpl— user cabinet profile edit form.users.register.tpl— registration form.users.auth.tpl— login/authentication page.users.edit.tpl— user properties editor (for moderators/admins).admin.users.tpl— user accounts management in the admin area.
Private Messages (PM)
pm.tpl— lists of inbox, sentbox, and archived messages.pm.send.tpl— send message form.admin.pm.tpl/admin.pm.stat.tpl— private messages statistics in the admin area.
Polls Module (Polls)
polls.tpl— poll results view.polls.poll.tpl— voting form.polls.standalone.tpl— poll view for widgets or sidebar blocks.admin.polls.tpl— polls management in the admin area.
Gallery Module (Gallery)
gallery.home.tpl— gallery homepage (list of albums).gallery.browse.tpl— photo grid of selected album.gallery.details.tpl— photo details page with comments/ratings.admin.gallery.tpl/admin.config.gallery.tpl— gallery management in the admin area.
9.5. Template Engine PHP API (for Developers)
Module and plugin developers interact with the template engine using an instance of the XTemplate class.
9.5.1. Creating an Instance and Compilation
The constructor accepts the path to the template file:
$t = new XTemplate(sed_skinfile('index'));
The sed_skinfile('filename') function checks for the presence of the template file in the active skin folder, returning the correct path.
9.5.2. Assigning Variables
The assign() method transfers variables to the template:
// Assign single variable
$t->assign('PAGE_TITLE', $page_title);
// Bulk assign via associative array
$t->assign(array(
'PAGE_ID' => $row['page_id'],
'PAGE_CAT' => $row['page_cat'],
'PAGE_OWNER' => $row['page_owner']
));
// Assign array keys prefixing them (e.g., outputs PAGE_ROW_VAR)
$t->assign($row, null, 'PAGE_ROW_');
9.5.3. Parsing Blocks
After variable assignment inside a block, the block is rendered using parse():
// Rendering nested block inside a loop
foreach ($items as $item) {
$t->assign(array(
'ITEM_ID' => $item['id'],
'ITEM_NAME' => $item['name']
));
$t->parse('MAIN.ITEMS_LIST.ITEM_ROW');
}
// Parsing parent blocks
$t->parse('MAIN.ITEMS_LIST');
$t->parse('MAIN');
Note: Conditions IF and loops FOR are evaluated automatically when parsing their parent blocks. Calling parse() on IF/FOR blocks in PHP code is not required.
9.5.4. Retrieving HTML Code and Displaying
- The
$t->text('MAIN')method returns the compiled HTML output as a PHP string (useful for caching or buffers). - The
$t->out('MAIN')method prints the compiled HTML directly to output.
9.6. Overriding Module and Plugin Templates
Seditio CMS supports overriding module and plugin styles without modifying system core files.
9.6.1. Overriding Plugin Templates
When a plugin loads templates (in direct, standalone, or popup modes), the core searches for the .tpl file in the following order of priority:
- Active theme folder:
/skins/{active_skin}/plugins/{plugin_code}.tpl(highest priority — allows overriding default layout under a specific theme). - Default plugin template:
/plugins/{plugin_code}/tpl/{plugin_code}.tpl(default template supplied with the plugin).
To support this lookup order in the code, templates must be loaded via sed_skinfile():
// Loads from skin folder if it exists, falling back to plugin directory
$t = new XTemplate(sed_skinfile('myplugin', true));
The second argument true tells sed_skinfile to look for a plugin template.
9.6.2. Overriding Module Templates
Templates for page views and categories inside the page module can be overridden for specific sections:
- If
page.news.tplexists in the skin folder, the page module automatically loads it for categorynews. - If no category-specific file is found, it falls back to the default
page.tpl.
9.7. Practical Guide to Creating a Skin
To create a new design theme (skin) for Seditio CMS, follow the structure of the default sympfy skin. Let's look at creating a theme with the code name mytheme.
[!NOTE]
Registered themes are managed in the control panel at
/admin/config?n=edit&o=core&p=skin(orindex.php?module=admin&m=config&n=edit&o=core&p=skinwithout SEF URLs), where the administrator can set the default theme.
9.7.1. Skin File Structure
Create a /skins/mytheme/ folder. Place the following folders and files inside:
mytheme.php— theme configuration and metadata.mytheme.ru.lang.php— Russian translations for theme layout phrases.mytheme.png— skin preview image in the admin panel (recommended size: 240x180 pixels).header.tpl— global header layout.footer.tpl— global footer layout.index.tpl— homepage layout.css/style.css— main stylesheet.js/— scripts directory.[!NOTE]
A skin does not need to duplicate all module templates (e.g.,
page.tpl,forums.tpl,users.tpl). If they are missing, Seditio will automatically load standard defaults from module directories (e.g.,/modules/page/tpl/page.tpl).
9.7.2. Theme Configuration (mytheme.php)
This file contains the mandatory SED header for Seditio, and overrides pagination, breadcrumbs, and plugin layout settings:
<?php /* ==================== Seditio - Website engine Copyright © Seditio Team https://seditio.org [BEGIN_SED] File=skins/mytheme/mytheme.php Version=1.0.0 Updated=2026-jul-06 Type=Skin Name=My Custom Theme Author=Developer Url=https://mysite.com Description=My custom responsive skin for Seditio CMS [END_SED] ==================== */ // Overriding pagination layout styles $cfg['pagination'] = '<li class="page-item">%s</li>'; $cfg['pagination_cur'] = '<li class="page-item active"><span class="page-link">%s</span></li>'; $cfg['pagination_arrowleft'] = '<i class="icon-chevron-left"></i>'; $cfg['pagination_arrowright'] = '<i class="icon-chevron-right"></i>'; // Overriding breadcrumbs separator $cfg['separator'] = '<i class="icon-arrow-right"></i>'; // Interface icons $out['ic_arrow_up'] = '<i class="icon-arrow-up"></i>'; $out['ic_folder'] = '<i class="icon-folder"></i>'; $out['ic_checked'] = '<i class="icon-check"></i>'; $out['ic_close'] = '<i class="icon-close"></i>'; // RecentItems plugin display masks $cfg['plu_mask_pages'] = '<span class="date">%3\$s</span> %1\$s ' . $cfg['separator'] . ' %2\$s<br />'; $cfg['plu_mask_comments'] = '<span class="date">%3\$s</span> %1\$s ' . $cfg['separator'] . ' %2\$s (%4\$s)<br />';
9.7.3. Theme Localization (mytheme.ru.lang.php or mytheme.en.lang.php)
The file contains translations of layout strings, grouped into the $skinlang array:
<?php /* ==================== [BEGIN_SED] File=skins/mytheme/mytheme.ru.lang.php Version=1.0.0 Updated=2026-jul-06 Type=Skin Name=My Custom Theme (Russian Localization) [END_SED] ==================== */ // Localization for header.tpl $skinlang['header']['Login'] = "Вход на сайт"; $skinlang['header']['Register'] = "Регистрация"; $skinlang['header']['Lostyourpassword'] = "Забыли пароль?"; // Localization for index.tpl $skinlang['index']['Newinforums'] = "Новое на форумах"; $skinlang['index']['Recentadditions'] = "Последние обновления"; $skinlang['index']['Online'] = "Сейчас на сайте";
In template files, localized phrases are loaded via: {PHP.skinlang.header.Login} or {PHP.skinlang.index.Online}.
9.7.4. Base Layout of Theme Files
[!IMPORTANT]
Automatic Header and Footer Inclusion:
In Seditio CMS, the header (
header.tpl) and footer (footer.tpl) templates are included automatically by core controllers (by executing/system/header.phpand/system/footer.phpbefore and after rendering). In content files (likeindex.tpl,page.tpl, etc.), you do not need to include the header or footer manually via the{FILE ...}directive.
Header Layout (header.tpl)
<!-- BEGIN: HEADER -->{HEADER_DOCTYPE}
<html>
<head>
<title>{HEADER_TITLE}</title>
{HEADER_METAS}
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="skins/{PHP.skin}/css/style.css" type="text/css" rel="stylesheet" />
{HEADER_CSS}
</head>
<body>
<!-- BEGIN: USER -->
<div class="userpanel">
<ul>
<li><span>{HEADER_LOGSTATUS}</span></li>
<!-- IF {HEADER_USER_ADMINPANEL} -->
<li>{HEADER_USER_ADMINPANEL}</li>
<!-- ENDIF -->
<!-- IF {HEADER_USER_PROFILE} -->
<li>{HEADER_USER_PROFILE}</li>
<!-- ENDIF -->
<!-- IF {HEADER_USER_LOGINOUT} -->
<li>{HEADER_USER_LOGINOUT}</li>
<!-- ENDIF -->
</ul>
</div>
<!-- END: USER -->
<header class="main-header">
<div class="logo">
<a href="{PHP.sys.dir_uri}" alt="{HEADER_TITLE}">{PHP.cfg.maintitle}</a>
</div>
<div class="menu">
{PHP.sed_menu.1.childrens}
</div>
</header>
<!-- END: HEADER -->
Footer Layout (footer.tpl)
<!-- BEGIN: FOOTER -->
<footer class="main-footer">
<div class="copyright">{FOOTER_BOTTOMLINE}</div>
<div class="stats">
{FOOTER_CREATIONTIME}<br />{FOOTER_SQLSTATISTICS}
</div>
{FOOTER_DEVMODE}
</footer>
{FOOTER_JAVASCRIPT}
</body>
</html>
<!-- END: FOOTER -->
Homepage Layout (index.tpl)
<!-- BEGIN: MAIN -->
<div class="content-wrapper">
<section class="intro">
<h1>{PHP.cfg.maintitle}</h1>
<p>{PHP.cfg.subtitle}</p>
</section>
<aside class="sidebar-box">
<h3>{PHP.skinlang.index.Online}</h3>
<p><a href="{PHP.out.whosonline_link}">{PHP.out.whosonline}</a> : {PHP.out.whosonline_reg_list}</p>
</aside>
</div>
<!-- END: MAIN -->
Comments: (0)