In this tutorial, you will learn how to create a simple theme for Scriptor CMS.

Since Scriptor does not force you to use third-party tools like bulky and sluggish template engines, there's nothing easier than creating a theme for Scriptor from scratch or preparing an already existing theme. In this tutorial, we will create a new theme based on the UIkit Framework.

As is the case with most other CMS platforms, Scriptor's templates contain HTML and PHP tags that are inserted where necessary to output the content. Let's start building our theme directory structure. Create a new theme folder named uikit-theme under the /site/themes/ directory. Download or clone UIkit, create the following file structure and place UIkit files in your themes directory as shown in the diagram below:

uikit-theme/
└───template.php
└───home.php
└───basic.php
└───_navigation.php
└───_functions.php
└───_head.php
└───_footer.php    
└───404.php    
└───scripts/
│  └───uikit.min.js
│  └───uikit-icons.min.js
│  └───main.js
└───css/
   └───uikit.min.css
   └───styles.css

Activate the theme

To activate newly created theme, open the /data/settings/custom.scriptor-config.php file and add the following line:

 'theme_path' => 'uikit-theme/',

Create start file

Now open/create template.php file, located in the root directory of your uikit-theme theme, with a text editor. The file act to be an entry point of our theme and is the first theme file that is loaded. We will use this file as a kind of switch for our template types:

<?php defined('IS_IM') or die('You cannot access this page directly');

include '_functions.php';

$template = $site->sanitizer->templateName($site->template);

if(file_exists(__DIR__."/$template.php")) { 
    include __DIR__."/$template.php";
} else {
    echo 'The template you specified was not found: '.__DIR__."/$template.php";
}

Every time a page is loaded, the code shown above determines which template is assigned to the page and tries to load a template file with that name. If there is no matching template file found, a message is displayed that a template with the name was not found.
As you can see in the folder structure diagram above, we use two different styles of template in our theme: home and basic. Where home is used to show the home (start) page content and basic represents the layout of the basic (default) pages.
Furthermore, we have included the file _functions.php at the top of the code. It contains some functions that we will use later in our template to create certain template areas such as the navigation, but more about that later.

Create shared areas

Since both templates home and basic use the same head, footer, and navigation areas, we will create those files at first. Let's start with the _head.php file, which as you can see, is also located in the root directory of our theme:

Note, the underscore (_) in the file name. Files beginning with an underscore are denied from direct access by .htaccess.

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title><?php echo $site->title; ?></title>
    <meta name="description" content="Simple template for Scriptor CMS">
    <!-- Mobile-friendly viewport -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="<?php echo $site->themeUrl; ?>css/uikit.min.css">
    <link rel="stylesheet" href="<?php echo $site->themeUrl; ?>css/styles.css">
</head>

Just a quick explanation of the PHP variables in the _head.php file above. This line outputs the current page's title:

    <title><?php echo $site->title; ?></title>

Use $site->themeUrl if you need the URL of the current theme including scheme and hostname:

    <link rel="stylesheet" href="<?php echo $site->themeUrl; ?>css/uikit.min.css">

Continue with the _footer.php file, which will later be used in our templates to represent the footer area:

<footer class="uk-section uk-section-secondary uk-padding-remove-bottom">
    <div class="uk-container uk-container-small">
        <div class="uk-grid uk-grid-large" data-uk-grid>
            <div class="uk-width-1-2@m">
                <h5>OUR COMPANY</h5>
                <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 
                    incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis 
                    nostrud cillum dolore eu fugiat nulla contact to: 
                    <a href="#" title="">info@company.com</a></p>
                <div>
                    <a href="" class="uk-icon-button" data-uk-icon="twitter"></a>
                    <a href="" class="uk-icon-button" data-uk-icon="facebook"></a>
                    <a href="" class="uk-icon-button" data-uk-icon="instagram"></a>
                </div>
            </div>
            <div class="uk-width-1-5@m">
                <h5>PRODUCTS</h5>
                <ul class="uk-list">
                    <li>Big Data</li>
                    <li>Marketing</li>
                    <li>Analytics</li>
                    <li>AI Lab</li>
                </ul>
            </div>
            <div class="uk-width-1-5@m">
                <h5>OUR COMPANY</h5>
                <ul class="uk-list">
                    <li>Team</li>
                    <li>Work</li>
                    <li>Culture</li>
                    <li>Contact Us</li>
                </ul>
            </div>
        </div>
    </div>
    <div class="uk-text-center uk-padding uk-padding-remove-horizontal uk-text-small">
        <span class="uk-text-muted">&copy; <?php echo date('Y'); ?> My Layout - 
        <a href="#">Created by My Company</a> | Built with <a href="https://scriptor-cms.info" 
        title="Simple flat-file CMS" target="_blank" data-uk-tooltip>SCRIPROR 
        <?php echo $site->version; ?></a></span>
    </div>
</footer>
<!-- OFFCANVAS -->
<div id="offcanvas-nav" data-uk-offcanvas="flip: true; overlay: false">
    <div class="uk-offcanvas-bar uk-offcanvas-bar-animation uk-offcanvas-slide">
        <button class="uk-offcanvas-close uk-close uk-icon" type="button" data-uk-close></button>
        <ul class="uk-nav uk-nav-default">
            <?php echo render_offcanvas_navi(['maxLevel' => 2]); ?>
        </ul>
    </div>
</div>
<!-- /OFFCANVAS -->
<script src="<?php echo $site->themeUrl; ?>scripts/uikit.min.js"></script>
<script src="<?php echo $site->themeUrl; ?>scripts/uikit-icons.min.js"></script>

Note off-canvas sidebar below in the source code, it slides in and out of the page, when you view the page on a mobile device. The function render_offcanvas_navi() is responsible for the rendering of the navigation elements. This function is basically identical to the render_navi_bar(), more information about it can be found here.

Since we use the same navigation across all template types, we also create this source code in a separate _navigation.php file:

<div class="nav" data-uk-sticky="uk-background-secondary uk-box-shadow-medium; 
    top: 100vh; animation: uk-animation-slide-top">
    <div class="uk-container uk-container-small">
        <nav class="uk-navbar uk-navbar-container uk-navbar-transparent" data-uk-navbar>
            <div class="uk-navbar-left">
                <div class="uk-navbar-item uk-padding-remove-horizontal">
                    <a class="uk-logo" title="Logo" href="/"><img src="<?php echo $site->siteUrl; 
                        ?>/data/uploads/simple-template/your-company-logo.png" alt="Logo"></a>
                </div>
            </div>
            <div class="uk-navbar-right">
                <ul class="uk-navbar-nav uk-visible@s">
                    <?php echo render_navi_bar(['maxLevel' => 2]); ?>
                </ul>
                <a class="uk-navbar-toggle uk-navbar-item uk-hidden@s" 
                    data-uk-toggle data-uk-navbar-toggle-icon href="#offcanvas-nav"></a>
            </div>
        </nav>
    </div>
</div>

Template functions

Note, in particular the use of the functions render_navi_bar() and render_offcanvas_navi(), we will add them both to our _functions.php file. But let's take a closer look at the particular parameters first, they are identical for both functions:

<?php echo render_navi_bar([
    'parent' => 0, 
    'maxLevel' => 2, 
    'sortBy' => 'position',
    'order' => 'asc',
    'exclude' => []
]); ?>

Parameters:

  • parent – (integer) (optional) Parent Page ID. Default value is 0 i.e. all pages with no parent.
  • maxLevel – (integer) (optional) The down-most level. Default is 1.
  • sortBy – (string) (optional) The sort by page field or attribute. Default value position.
  • order – (string) (optional) Descending or ascending sort order. Default value asc.
  • exclude – (array) (optional) An array of page ID's to be excluded from the navigation. Is empty by default.

Now, add the function render_navi_bar() shown above, in the _functions.php file:

<?php

use Scriptor\Scriptor;
use Scriptor\Site;

/**
 * Function for rendering the navigation 
 * elements of the navigation bar.
 * 
 * @param array $options Extra options to customize the rendering of the elements.
 *   - parent (integer) (optional) Parent Page ID. Default value is '0' i.e. all pages with no parent.
 *   - maxLevel (integer) (optional) The down-most level. Default is '1'.
 *   - sortBy (string) (optional) The sort by page field or attribute. Default value position.
 *   - order (string) (optional) Descending or ascending sort order. Default value 'asc'.
 *   - exclude (array) (optional) An array of page ID's to be excluded from the navigation. Is empty by default.
 * 
 * @return string
 */
function render_navi_bar($options = [])
{
    $site = Scriptor::getSite();
    $navigation = '';
    $defaults = [
        'parent' => 0,
        'maxLevel' => 1,
        'exclude' => []
    ];
    $configs = array_merge($defaults, $options);
    $pageLevels = $site->getPageLevels($configs);

    if($pageLevels) {
        foreach($pageLevels[$configs['parent']] as $page) {
            if(in_array($page->id, $configs['exclude'])) continue;

            if(empty($pageLevels[$page->id])) {
                $navigation .= '<li><a href="'.$site->siteUrl.'/'.
                    Site::getPageUrl($page, $site->pages) .'">'.$page->name.'</a></li>';
            } else {
                $configs['parent'] = $page->id;
                $configs['maxLevel'] = $configs['maxLevel'] - 1;
                $navigation .= '<li><a href="#">'.$page->name.'</a>';
                $navigation .= '    <div class="uk-navbar-dropdown">';
                $navigation .= '        <ul class="uk-nav uk-navbar-dropdown-nav">';
                $navigation .=              render_navi_bar($configs);      
                $navigation .= '        </ul>';
                $navigation .= '    </div>';
                $navigation .= '</li>';
            }
        }
    }
    return $navigation;
}

/**
 * Function for rendering the navigation 
 * elements of the offcanvas navigation.
 * 
 * @param array $options Extra options to customize the rendering of the elements.
 *   - parent (integer) (optional) Parent Page ID. Default value is '0' i.e. all pages with no parent.
 *   - maxLevel (integer) (optional) The down-most level. Default is '1'.
 *   - sortBy (string) (optional) The sort by page field or attribute. Default value position.
 *   - order (string) (optional) Descending or ascending sort order. Default value 'asc'.
 *   - exclude (array) (optional) An array of page ID's to be excluded from the navigation. Is empty by default.
 * 
 * @return string
 */
function render_offcanvas_navi($options = []) 
{
    $site = Scriptor::getSite();
    $navigation = '';
    $defaults = [
        'parent' => 0,
        'maxLevel' => 1,
        'exclude' => []
    ];
    $configs = array_merge($defaults, $options);
    $pageLevels = $site->getPageLevels($configs);

    if($pageLevels) {
        foreach($pageLevels[$configs['parent']] as $page) {
            if(in_array($page->id, $configs['exclude'])) continue;

            if(empty($pageLevels[$page->id])) {
                $navigation .= '<li><a href="'.$site->siteUrl.'/'.
                    Site::getPageUrl($page, $site->pages) .'">'.$page->name.'</a></li>';
            } else {
                $configs['parent'] = $page->id;
                $configs['maxLevel'] = $configs['maxLevel'] - 1;
                $navigation .= '<li class="uk-parent"><a href="#">'.$page->name.'</a>';
                $navigation .= '    <ul class="uk-nav-sub">';
                $navigation .=          render_navi_bar($configs);      
                $navigation .= '    </ul>';
                $navigation .= '</li>';
            }
        }
    }
    return $navigation;
}

Templates

Next, we look at the actual template files, in which we will include the shared template areas we have created. We will begin with the home.php template:

<?php defined('IS_IM') or die('You cannot access this page directly'); ?>
<!DOCTYPE html>
<html lang="en">
<?php include '_head.php'; ?>
<body>
    <div class="top-wrap uk-position-relative uk-background-secondary">
        <?php include '_navigation.php' ?>
        <div class="uk-cover-container uk-light uk-flex uk-flex-middle top-wrap-height">
            <div class="uk-container uk-container-small uk-flex-auto top-container 
                uk-position-relative uk-margin-medium-top" data-uk-parallax="y: 0,50; easing:0; opacity:0.2">
                <div class="uk-width-1-2@s" data-uk-scrollspy="cls: uk-animation-slide-right-medium; 
                    target: > *; delay: 150">
                    <h6 class="uk-text-primary uk-margin-small-bottom">RESEARCH</h6>
                    <h1 class="uk-margin-remove-top">Innovation in your hands.</h1>
                    <p class="subtitle-text uk-visible@s">Tempor incididunt ut labore et dolore magna aliqua. 
                        Ut enim ad minim veniam, quis nostrud exercitation ullamco </p>
                    <a href="#" title="Learn More" class="uk-button uk-button-primary uk-border-pill" 
                        data-uk-scrollspy-class="uk-animation-fade">LEARN MORE</a>
                </div>
            </div>
        </div>
        <section id="content" class="uk-section uk-section-default uk-background-default">
            <div class="uk-container uk-container-small">
                <h2 class="uk-margin-small uk-margin-remove-top"><?php echo $site->title; ?></h2>
                <?php echo $site->render('content'); ?>
            </div>
        </section>  
    </div>
    <?php include '_footer.php'; ?>
</body>
</html>

Create the basic.php template file to display all the pages that are assigned to the basic template:

<?php defined('IS_IM') or die('You cannot access this page directly'); ?>
<!DOCTYPE html>
<html lang="en">
<?php include '_head.php'; ?>
<body>
    <div class="top-wrap uk-position-relative">
        <?php include '_navigation.php' ?>
        <section id="content" class="uk-section uk-section-default uk-padding-large uk-background-default">
            <div class="uk-container uk-container-small uk-margin-top uk-padding-top">
                <h1 class="uk-margin-top"><?php echo $site->title; ?></h1>
                <?php echo $site->render('content'); ?>
            </div>
        </section>  
    </div>
    <?php include '_footer.php'; ?>
</body>
</html>

What's missing now is the 404.php and a stylesheet styles.css files. While you can simply copy the first one from the default basic theme, the content of the styles.css file might look something like this:

body {
    margin: 0;
    padding: 0;
    font-size: 1em;
    text-rendering: optimizeLegibility;
    font-smoothing: antialiased;
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
    -webkit-overflow-scrolling: touch;
    -webkit-tap-highlight-color: transparent;
    -webkit-text-size-adjust: none;
    -webkit-touch-callout: none;
    -webkit-font-smoothing: antialiased;
}
.nav {
    position: absolute;
    z-index: 99;
    top: 0;
    left: 0;
    right: 0;
    background: #222;
}
.uk-sticky-placeholder {
    height: 0 !important;
}
.top-wrap::before, .overlay-wrap::before {
    position: absolute;
    top:0;
    right:0;
    left: 0;
    bottom: 0;
    content: '';
    z-index: 1;
}
.top-wrap-height {
    height: 80vh;
    min-height: 460px;
    transition: height 0.25s;
}
.uk-subnav-pill > * > :first-child {
    padding: 0.5rem 1rem;
    background-color: rgba(0, 0, 0, 0.04);
}
.top-container {
    z-index: 2;
}
.subtitle-text {
    font-size: 1.2em;
    opacity: 0.7;
    font-weight: 300;
}
.uk-navbar-dropdown {
    background: #3a3838;
    color: #c5c5c5;
}
.uk-navbar-dropdown-nav>li>a:focus, .uk-navbar-dropdown-nav>li>a:hover {
    color: #d8d8d8;
}

Finally, I created the following page structure in the Scriptor editor for demonstration purposes:

Demo image

Where Home is the only page assigned to the home template, all others are assigned the basic.

Check out the demo page.

If you want to check out the theme, you can download it here.