Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Creating a theme based on classic

From MoodleDocs

Moodle 3.7


This is a tutorial for how to create a new theme based on the Classic theme.

Moodle 3.7 includes a new core theme named "Classic" which is a starting point for themers wanting to build Moodle theme using a 3 column layout without the Boost navdrawer and settings menus.

Getting started

What is a theme? A theme in Moodle is just another type of plugin that can be developed. Themes are responsible for setting up the structure of each page and have the ability to customise the output of any page in Moodle.

This tutorial is based on the tutorial https://docs.moodle.org/dev/Creating_a_theme_based_on_boost. You can download it or view the source code for it here: https://github.com/bmbrands/theme_picture

Choosing a name

Your new theme will need a name. Try and think of something short and memorable - and make sure it is not a name that has already been used by someone else. A quick search on the moodle.org/plugins can save you a lot of work renaming things later.

Let's call our new example theme "picture" as we will add some settings to allow "pictures" in various places in Moodle.

Starting files

As a plugin, themes must start with the basic structure of a plugin in Moodle. See https://docs.moodle.org/dev/Tutorial#The_skeleton_of_your_plugin for an overview of the files common to all plugins in Moodle.

Following this guide you can start creating your own theme. First, we create the folder for the new theme under under "/theme/" folder in the Moodle root directory.

/theme/picture/

Now we need to add some standard plugin files to our theme. First is version.php

/theme/picture/version.php

<?php
// Every file should have GPL and copyright in the header - we skip it in tutorials but you should not skip it for real.

// This line protects the file from being accessed by a URL directly.
defined('MOODLE_INTERNAL') || die();

// This is the version of the plugin.
$plugin->version = 2019030400;

// This is the version of Moodle this plugin requires.
$plugin->requires = 2018051700;

// This is the component name of the plugin - it always starts with 'theme_'
// for themes and should be the same as the name of the folder.
$plugin->component = 'theme_picture';

// This is a list of plugins, this plugin depends on (and their versions).
$plugin->dependencies = [
    'theme_classic' => 2018120700
];

// This is a stable release.
$plugin->maturity = MATURITY_STABLE;

// This is the named version.
$plugin->release = 1.0;

We also need a language file so that all our strings can be translated into different languages. The name of this file is the component name of our plugin and it sits in the lang/en/ folder for our plugin. We can include translations of our plugin, but we can also provide translations via the https://lang.moodle.org/ website once our plugin has been published to the plugins database at http://www.moodle.org/plugins/.

/theme/picture/lang/en/theme_picture.php

<?php
// Every file should have GPL and copyright in the header - we skip it in tutorials but you should not skip it for real.

// This line protects the file from being accessed by a URL directly.
defined('MOODLE_INTERNAL') || die();

// A description shown in the admin theme selector.
$string['choosereadme'] = 'Theme picture is a child theme of the Classic. It adds the ability to upload background photos.';
// The name of our plugin.
$string['pluginname'] = 'Picture';
// We need to include a lang string for each block region.
$string['region-side-pre'] = 'Left';
$string['region-side-post'] = 'Right';

Theme specific files

Theme plugins have a few more standard files they need to define.

Themes require a favicon file to show in the address bar. See [Favicon].

pix/favicon.ico

(Image file not shown).

Themes also require an example screenshot to be displayed in the theme selector.

pix/screenshot.jpg

(Image file not shown).

Themes require a lib.php file. This file contains callbacks used by various API's in Moodle. Initially this file can be empty, but as we add features to our theme we will need to add some functions here.

lib.php

<?php

// Every file should have GPL and copyright in the header - we skip it in tutorials but you should not skip it for real.

// This line protects the file from being accessed by a URL directly.
defined('MOODLE_INTERNAL') || die();

// We will add callbacks here as we add features to our theme.

Theme config goes in a config.php file. This is one of the most important files in our theme. Once we add this file we will be ready to test our theme for the first time.

config.php

<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Picture config.
 *
 * @package   theme_picture
 * @copyright 2016 Damyon Wiese
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

// This line protects the file from being accessed by a URL directly.
defined('MOODLE_INTERNAL') || die();

// $THEME is defined before this page is included and we can define settings by adding properties to this global object.

// The first setting we need is the name of the theme. This should be the last part of the component name, and the same
// as the directory name for our theme.
$THEME->name = 'picture';

// This setting list the style sheets we want to include in our theme. Because we want to use SCSS instead of CSS - we won't
// list any style sheets. If we did we would list the name of a file in the /styles/ folder for our theme without any css file
// extensions.
$THEME->sheets = [];

// This is a setting that can be used to provide some styling to the content in the TinyMCE text editor. This is no longer the
// default text editor and "Atto" does not need this setting so we won't provide anything. If we did it would work the same
// as the previous setting - listing a file in the /styles/ folder.
$THEME->editor_sheets = [];

// This is a critical setting. We want to inherit from theme_classic because it provides a great starting point for SCSS bootstrap4
// themes. We have added add more than one parent here to inherit from multiple parents, and if we did they would be processed in
// order of importance (later themes overriding earlier ones). Things we will inherit from the parent theme include
// styles and mustache templates and some (not all) settings.
$THEME->parents = ['boost', 'classic'];

// A dock is a way to take blocks out of the page and put them in a persistent floating area on the side of the page.
// does not support a dock so we won't either - but look at bootstrapbase for an example of a theme with a dock.
$THEME->enable_dock = false;

// This is an old setting used to load specific CSS for some YUI JS. We don't need it in Classic based themes because Classic
// provides default styling for the YUI modules that we use. It is not recommended to use this setting anymore.
$THEME->yuicssmodules = array();

// Most themes will use this rendererfactory as this is the one that allows the theme to override any other renderer.
$THEME->rendererfactory = 'theme_overridden_renderer_factory';

$THEME->prescsscallback = 'theme_picture_get_pre_scss';

// Since we are using 2 parent themes the correct location of the layout files needs to be defined. For this theme we need the multiple
// column layouts.
$THEME->layouts = [
    // Most backwards compatible layout without the blocks - this is the layout used by default.
    'base' => array(
        'theme' => 'classic',
        'file' => 'columns.php',
        'regions' => array(),
    ),
    // Standard layout with blocks, this is recommended for most pages with general information.
    'standard' => array(
        'theme' => 'classic',
        'file' => 'columns.php',
        'regions' => array('side-pre', 'side-post'),
        'defaultregion' => 'side-pre',
    ),
    // Main course page.
    'course' => array(
        'theme' => 'classic',
        'file' => 'columns.php',
        'regions' => array('side-pre', 'side-post'),
        'defaultregion' => 'side-pre',
        'options' => array('langmenu' => true),
    ),
    'coursecategory' => array(
        'theme' => 'classic',
        'file' => 'columns.php',
        'regions' => array('side-pre'),
        'defaultregion' => 'side-pre',
    ),
    // Part of course, typical for modules - default page layout if $cm specified in require_login().
    'incourse' => array(
        'theme' => 'classic',
        'file' => 'columns.php',
        'regions' => array('side-pre'),
        'defaultregion' => 'side-pre',
    ),
    // The site home page.
    'frontpage' => array(
        'theme' => 'classic',
        'file' => 'columns.php',
        'regions' => array('side-pre', 'side-post'),
        'defaultregion' => 'side-pre',
        'options' => array('nofullheader' => true),
    ),
    // Server administration scripts.
    'admin' => array(
        'theme' => 'classic',
        'file' => 'columns.php',
        'regions' => array('side-pre'),
        'defaultregion' => 'side-pre',
    ),
    // My dashboard page.
    'mydashboard' => array(
        'theme' => 'classic',
        'file' => 'columns.php',
        'regions' => array('side-pre', 'side-post'),
        'defaultregion' => 'side-pre',
        'options' => array('nonavbar' => true, 'langmenu' => true, 'nocontextheader' => true),
    ),
    // My public page.
    'mypublic' => array(
        'theme' => 'classic',
        'file' => 'columns.php',
        'regions' => array('side-pre'),
        'defaultregion' => 'side-pre',
    ),
    'login' => array(
        'theme' => 'boost',
        'file' => 'login.php',
        'regions' => array(),
        'options' => array('langmenu' => true),
    ),

    // Pages that appear in pop-up windows - no navigation, no blocks, no header.
    'popup' => array(
        'theme' => 'classic',
        'file' => 'contentonly.php',
        'regions' => array(),
        'options' => array('nofooter' => true, 'nonavbar' => true),
    ),
    // No blocks and minimal footer - used for legacy frame layouts only!
    'frametop' => array(
        'theme' => 'classic',
        'file' => 'contentonly.php',
        'regions' => array(),
        'options' => array('nofooter' => true, 'nocoursefooter' => true),
    ),
    // Embeded pages, like iframe/object embeded in moodleform - it needs as much space as possible.
    'embedded' => array(
        'theme' => 'boost',
        'file' => 'embedded.php',
        'regions' => array()
    ),
    // Used during upgrade and install, and for the 'This site is undergoing maintenance' message.
    // This must not have any blocks, links, or API calls that would lead to database or cache interaction.
    // Please be extremely careful if you are modifying this layout.
    'maintenance' => array(
        'theme' => 'boost',
        'file' => 'maintenance.php',
        'regions' => array(),
    ),
    // Should display the content and basic headers only.
    'print' => array(
        'theme' => 'classic',
        'file' => 'contentonly.php',
        'regions' => array(),
        'options' => array('nofooter' => true, 'nonavbar' => false),
    ),
    // The pagelayout used when a redirection is occuring.
    'redirect' => array(
        'theme' => 'boost',
        'file' => 'embedded.php',
        'regions' => array(),
    ),
    // The pagelayout used for reports.
    'report' => array(
        'theme' => 'classic',
        'file' => 'columns.php',
        'regions' => array('side-pre'),
        'defaultregion' => 'side-pre',
    ),
    // The pagelayout used for safebrowser and securewindow.
    'secure' => array(
        'theme' => 'classic',
        'file' => 'secure.php',
        'regions' => array('side-pre'),
        'defaultregion' => 'side-pre'
    )
];

Ready set go!

If you have been following along - now we are at the point where we can actually install and test our new theme. Try it now by visiting the admin notifications page to install the new plugin, and then choosing the new theme from the theme selector.

[Theme selector]

When you choose the new theme - you will find that it looks exactly the same as Classic. At this point with our minimal configuration - we are inheriting almost everything from our parent theme including styles and templates. You will notice though that we don't inherit the settings from our parent theme.

Setup your Sass files

Once you have a theme that has been installed into your Moodle setup it is time to start styling it. The core Boostrap scss is very well suited for customization using Sass variables and overriding component. On Get bootstrap.com there is some documentation on Sass variables and in theme/boost/scss/boostrap/_variables.scss you will see the full list of variables that can be changed.

Before we start the theme needs to be able to return the complete set of Sass files that are required for Moodle and our custom Sass. So as a start we will be editing the config again:

theme/picture/config.php

// This is the function that returns the SCSS source for the main file in our theme.
$THEME->scss = function($theme) {
    return theme_picture_get_main_scss_content($theme);
};

The variable $THEME->scss is configured to be a callback function that when called returns all the Scss source files from our parent theme and the picture theme. Once the callback function is defined our Theme's lib.php needs to contain the callback function.

theme/picture/lib.php

	/**
 * Returns the main SCSS content.
 *
 * @param theme_config $theme The theme config object.
 * @return string All fixed Sass for this theme.
 */
function theme_picture_get_main_scss_content($theme) {
    global $CFG;

    $scss = '';

    $fs = get_file_storage();

    // Main CSS - Get the CSS from theme Classic.
    $scss .= file_get_contents($CFG->dirroot . '/theme/classic/scss/classic/pre.scss');
    $scss .= file_get_contents($CFG->dirroot . '/theme/classic/scss/preset/default.scss');
    $scss .= file_get_contents($CFG->dirroot . '/theme/classic/scss/classic/post.scss');

    // Pre CSS - this is loaded AFTER any prescss from the setting but before the main scss.
    $pre = file_get_contents($CFG->dirroot . '/theme/picture/scss/pre.scss');

    // Post CSS - this is loaded AFTER the main scss but before the extra scss from the setting.
    $post = file_get_contents($CFG->dirroot . '/theme/picture/scss/post.scss');

    // Combine them together.
    return $pre . "\n" . $scss . "\n" . $post;
}

The function theme_picture_get_main_scss_content uses the scss files from classic, the default.scss file from classic contains @import statements to include the files from theme Boost so we can be sure we have all the css we need.

theme/classic/scss/preset/default.scss

// Import FontAwesome.
@import "fontawesome";

// Import All of Bootstrap
@import "bootstrap";

// Import Core moodle CSS
@import "moodle";

To complete the setup all we need to do is create the pre.scss and post.scss files that will contain our Sass variables and other Css. The pre.scss file is the file that needs to be loaded first before the files from theme classic are imported. It will contain the variable we configure for things like fonts, colours and spacing. The post.scss file will contain all other custom css.

theme/picture/scss/pre.scss

// We need larger buttons.
$btn-padding-y:         0.5rem !default;
$btn-padding-x:         1.1rem !default;

theme/picture/scss/post.scss

// We like our buttons very round.
.btn {
  border-radius: 1.078em;
  font-family: $font-family-sans-serif;
  font-size: 0.875em;
}

You might have noticed the "!default" statement after the variables in our pre.scss file. This means the variable is set if the variable has not been defined before. When inspecting the Bootstrap variables defined in theme/boost/scss/bootstrap/_variables.scss you will see all variable have a "!default" statement meaning we can change any variable!

With the preset files in place we can select our theme from the available installed themes (If you did not do that already) and purge changes. You should be able to see the Classic 3 column layout and our round buttons.

Adding custom settings to our theme

That's it for this tutorial, but there are more Themes docs to browse.