Modules

Here you'll find everything needed to start developing your first module, from structure to update routine & packaging.

What is a module ?

Quick start: copy module-skeleton, a small working module (one table, one editor form, one mobile page, and tools/pack.sh to build the zip), then rename it. The Job module in the core is a full-sized example.

Modules are the core of SiberianCMS, they run frontend, backend, features & even more.

A module can be a single front page with new features or settings, or a complete feature for SiberianCMS to be used in your Mobile apps and managed in your App editor.

Here you can find a module skeleton to start with Siberian module Skeleton, Job

Modules are installed in the app/local/modules folder, for more information about the inheritance see this page module/core-inheritance

Structure

Folder structure

ModuleName
├─ Controller
│  └─ Default.php   /** If you need to override the default controller Classes */
├─ controllers
│  ├─ Backoffice
│  │  └─ ModuleNameController.php
│  └─ Mobile
│     └─ ModuleNameController.php
├─ features
│  └─ modulename
│     ├─ feature.json
│     ├─ assets
│     │  └─ templates   // Ionic templates, copied to the apps
│     ├─ icons
│     ├─ js
│     └─ scss
├─ init.php        // see "Init" in Basic usage
├─ Form
├─ Model
├─ View
├─ resources
│  ├─ db
│  │  ├─ data
│  │  └─ schema
│  ├─ media 
│  │  └─ library
│  ├─ translations
│  │  ├─ default
│  │  │  └─ mymodule.po
│  │  ├─ en
│  │  └─ [...]
│  └─ design
└─ package.json

Basics

package.json

The package.json is used by the Installer to know the requirements, and routines to run during the installation/update process

type is one of module, layout, icons or template; dependencies.system.type is SAE, MAE or PE, and the install is refused if the running edition or version is lower than required.

{
  "name": "ModuleName",
  "description": "Module description",
  "type": "module",
  "version": "1.0",
  "dependencies": {
    "system": {
      "type": "SAE",
      "version": "5.1.0"
    },
    "modules": {
      "OtherModule": "2.1.0"
    }
  }
}

resources/db/schema

We use resources/db/schema/table_name.php to describe the table schema; below an example of a basic table.

This file(s) reflects your database at it's latest version, each time you update your module, the local schema is compared against this file and updating

Note: the schema only add fields if they are new or missing, fields are never removed.

Only files named table_name.php are read from this folder (the file name, without .php, is the table name). Any other file, such as an install.sql, a .DS_Store, a backup copy or a sub folder, is ignored; files named *.ec.php (encrypted modules) are skipped as well. Older versions created such a stray file as a table of that name, and the install failed with Unable to create table 'install.sql'.

<?php
/**
 * Schema definition for "table_name"
 */
$schemas = (!isset($schemas)) ? [] : $schemas;
$schemas["table_name"] = [
    "mytable_id" => [
        "type" => "int(11) unsigned",
        "auto_increment" => true,
        "primary" => true,
    ],
    "admin_id" => [
        "type" => "int(11) unsigned",
        "is_null" => true,
        "foreign_key" => [
            "table" => "admin",
            "column" => "admin_id",
            "name" => "FK_MY_MODULE_ADMIN_ID_ADMIN_AMIN_ID", // Must be unique!
            "on_update" => "CASCADE",
            "on_delete" => "CASCADE",
        ],   
    ],
    "name" => [
        "type" => "varchar(50)",
        "default" => "default_value",
        "index" => [
            "key_name" => "IDX_MYTABLE_NAME",
            "index_type" => "BTREE",
            "is_null" => false,
            "is_unique" => false,
        ],
    ],
    "created_at" => [
        "type" => "datetime",
    ],
    "updated_at" => [
        "type" => "datetime",
    ],
];

List of available options

Key Type Usage, values, comment
type String int(11) unsigned, varchar(50), datetime, etc ...
auto_increment Boolean The column is AUTO_INCREMENT. Only the presence of the key is checked, so leave it out instead of setting it to false
primary Boolean If multiple columns are defined as primary they will be part of a composite primary key. Only the presence of the key is checked
is_null Boolean The column accepts NULL (DEFAULT NULL when there is no default). Only the presence of the key is checked, so leave it out instead of setting it to false
unique Boolean The column is UNIQUE
default mixed Default value of the column
collation String Collation of the column, ex: utf8mb4_unicode_ci
foreign_key Array() See the Foreign key options below
index Array() See the Index options below. It must be an array: true is not valid (PHP 8 throws a TypeError when the column is added to an existing table)

Foreign Key options

Key Type Usage, values, comment
table String Referenced table
column String Referenced column in table
name String Foreign key name (ex: FK_TABLENAME_KEYNAME_REFTABLENAME_REFKEYNAME)
on_update String NO ACTION, SET NULL, CASCADE, RESTRICT
on_delete String NO ACTION, SET NULL, CASCADE, RESTRICT

Index options

Key Type Usage, values, comment
key_name String Index name, use the same on multiple columns for composite indexes
index_type String BTREE, HASH
is_null Boolean
is_unique Boolean

resources/db/data

This folder is used to insert default values when installing, or updating a Module.

The php files of this folder are executed when installing and/or updating the module, they should reflect the required data as the latest version. Installing or updating the same version again must be safe: write the data as upserts (insertOrUpdate(), a lookup before an insert), never as a blind insert.

Which files run:

File name Behaviour
anything.php Executed on every install and update
*install.php Backward compatibility for old modules: executed only the first time the module is installed
*.ec.php Never executed (encrypted modules)
A name whose last character before .php is a digit or a dot Never executed. This is the old 1.2.3.php version style, and it is detected with a pattern (/.*([0-9\.]+)\.php$/), so feature1.php or step2.php are skipped as well

Never name a file install.php, *install.php or %VERSION%.php (%VERSION% is a version string), and do not end a file name with a digit.

Best practice:

features/modulename/feature.json

This file is the key for every In app features. Every features/*/feature.json of a module is read by the Installer, the js/scss/css listed in files are minified into one lazy-loaded bundle (features/<code>/<code>.bundle.min.js) and the assets folder is copied to the apps.

{
    "name": "ModuleName",
    "code": "modulename",
    "version": "1.0.0",
    "category": "contact",
    "model": "ModuleName_Model_MainClass",
    "desktop_uri": "modulename/application/",
    "routes": [
        {
            "root": true,
            "state": "modulename-home",
            "controller": "ModuleNameHome",
            "url": "modulename/mobile_home/index/value_id/:value_id",
            "template": "l1/home.html",
            "cache": false
        },
        {
            "state": "modulename-view",
            "controller": "ModuleNameView",
            "url": "modulename/mobile_view/index/value_id/:value_id/item_id/:item_id",
            "template": "l1/view.html",
            "cache": false
        }
    ],
    "layouts": [
        1
    ],
    "icons": [
        "icons/modulename1-flat.png",
        "icons/modulename2-flat.png",
        "icons/modulename3-flat.png"
    ],
    "files": [
        "js/services/modulename.js",
        "js/factory/modulename.js",
        "js/controllers/modulename.js",
        "scss/modulename.scss"
    ],
    "compile": true,
    "use_account": true,
    "only_once": true,
    "load_on_start": false,
    "on_start_factory": null
}

All paths in icons and files are relative to the folder of the feature.json. A route template is relative to features/modulename/assets/templates/ (here assets/templates/l1/home.html).

List of options

Key Type Usage, values, comment
name String Your feature name
code String This code is unique to your Feature, Module
category String social, media, contact, monetization, customization, integration, events, misc, or _service for a service module (no option is created in the editor)
version String Not used here see package.json for the version
model String Default model class used in the editor
desktop_uri String Default controller class uri used in the editor
routes Array Ionic states of the feature, see Route options. The route with "root": true is the entry point of the feature
icons Array Icons of the feature, relative to the feature.json folder
files Array js, scss and css files bundled with the feature (a file path containing .. is ignored)
layouts Array Layouts offered for the feature in the editor
compile Boolean Leave true by default
use_account Boolean If the feature requires user to login set true
only_once Boolean Whether an app may have this feature only once, or more
is_ajax Boolean Defaults to true
load_on_start Boolean If the feature must be loaded right after the Application starts
on_start_factory String When load_on_start is set to true, Factory.onStart function will be executed, where Factory is the factory class name
open_callback_class, lazy_load String Angular factory whose openCallback(feature) is called when the feature is opened instead of a route, and the comma separated bundles to load before
social_sharing, use_nickname, use_ranking, use_civility, use_mobile, use_birthdate, use_critical_push Boolean Feature capabilities, all default to false
position, custom_fields Integer, Array Optional, position of the feature in its category, extra option fields

name, code, model, desktop_uri, routes and icons are mandatory: a feature.json missing one of them is silently ignored.

Route options

Key Type Usage, values, comment
state String Ionic state name
url String Appended to BASE_PATH
controller String Angular controller
template String Template path, relative to assets/templates/
templateHTML String Inline template, used instead of template
layouts Object Template folder per layout id (plus a default), used with template
externalTemplate Boolean template is a full path, not relative to assets/templates/
root Boolean Entry point of the feature
cache Boolean View cache, enabled unless set to false
autoregister Boolean Set to false to not register the state automatically

Translations

Siberian uses .po files, which are Gettext source files.

First create a new file named mymodule.po then place this file in the translations/default folder

If you want to ship your module with translations, place files in directories named with the locale code, example: english will be in translations/en/mymodule.po

Below as an example the contact.po file.

Be sure to not use existing filename which are located in SIBERIANCMS_ROOT/languages/base otherwise your translations won't work.

msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2019-03-28T15:24:21+01:00\n"
"PO-Revision-Date: 2019-03-28T15:24:21+01:00\n"
"Language: \n"

msgctxt "contact"
msgid "Cover image"
msgstr "Illustration"

msgctxt "contact"
msgid "Name"
msgstr "Nom"

#, mobile
msgctxt "contact"
msgid "Facebook"
msgstr "Facebook"

#, mobile
msgctxt "contact"
msgid "Twitter"
msgstr "Twitter"

#, mobile
msgctxt "contact"
msgid "Website"
msgstr "Site web"

msgctxt "contact"
msgid "String to translate"
msgstr "Phrase à traduire"

msgctxt "contact"
msgid "Hello, I'm %s"
msgstr "Bonjour je suis %s"

List of options

Key Type Usage, values, comment
#, mobile String Add this line if the string is translated inside the Mobile app
msgctxt String This is you context key, generally it's the module code
msgid String The original string, used as a key
msgstr String In default folder, msgstr = msgid, in translation files, msgstr is the translated sentence

To have the strings of your module extracted into your default file, register it in your init.php and set $_config["extract"] = true; in config.php while developing (see Job/init.php).

<?php
use Siberian\Translation;

$init = function ($bootstrap) {
    Translation::registerExtractor("mymodule", "MyModule", path("/app/local/modules/MyModule/resources/translations/default/mymodule.po"));
};

How to translate

Inside editor & backoffice

<?php

// Simple translation
echo p__("context_key", "String to translate");

// Will render [en]"String to translate" , [fr]"Phrase à traduire", as per the .po file below

// sprintf translation
$userName = "John";
echo p__("context_key", "Hello, I'm %s", $userName);

// Will render [en]"Hello, I'm John", [fr]"Bonjour je suis John", as per the .po file below


Inside mobile apps, note key & context are swapped compared to the php function

// From the controllers, include $translate factory
$translate.instant("Hello, I'm %s", "context_key");
// From any template
<div class="item">
    {{ "Hello, I'm %s" | translate:"context_key" }}
</div>

Design

Only the flat design is shipped today (design_code() always returns flat).

Everything you need to organize, style & interact with your Feature is located in the design folder

ModuleName
├─ [...]
├─ resources
│  └─ design
│     ├─ desktop
│     │  └─ flat
│     │     ├─ css
│     │     │  └─ modulename.css
│     │     ├─ js
│     │     │  └─ modulename.js
│     │     ├─ images
│     │     │  └─ customization
│     │     │     └─ layout
│     │     │        └─ modulename
│     │     │           ├─ layout-1.png
│     │     │           └─ layout-2.png
│     │     ├─ layout
│     │     │  └─ modulename.xml
│     │     └─ template
│     │        ├─ company
│     │        │  └─ [...]
│     │        └─ modulename
│     │           ├─ index.phtml
│     │           └─ application
│     │              ├─ edit.css
│     │              ├─ edit.js
│     │              └─ edit.phtml
│     └─ email
│        ├─ layout
│        └─ template
└─ [...]

modulename.xml example

This is the minimum required information in the default layout.xml, the file should be named as the module itself, lower-cased so job.xml in our case.

<?xml version="1.0" encoding="UTF-8"?>
<layout>
    <modulename_application_edit>
        <views>
            <content class="application_view_customization_features_edit_tabbareditor" 
                     template="application/customization/features/edit/tabbar_editor.phtml" />
            <content_editor class="core_view_default" 
                            template="modulename/application/edit.phtml" />
        </views>
    </modulename_application_edit>
</layout>

The tag name is the request path with underscores: modulename_application_edit is the action edit of the application controller of the modulename module (see Job/resources/design/desktop/flat/layout/job.xml).

The section content is the default for the feature editor tab, you should not need to change it.

The section content_editor should point to the feature editor template

here: the short path modulename/application/edit.phtml referring to ModuleName/resources/design/template/modulename/application/edit.phtml

Cache

While developing a module you will have to manually rebuild what we call design cache

You can do so with the Siberian cli by running ./cli cache:design sae (replace sae with your edition, sae|mae|pe)

Note: when installing and/or updating a module with the regular zip package, the cache is automatically cleared for the users.

Background images

Editor side

In the file edit.phtml which handles the Editor UI for your module, you must add a background section like below.

<div class="background-images-import">
    <?php echo $this->importBackground($option_value); ?>
</div>

<script type="text/javascript">
$(document).ready(function () {
    // Bind form, only if the current form is not binded from a parent (this could bind it twice)
    bindForms('.background-images-import');
});
</script>

This is as easy as the code below, as everything is linked to the feature, and cropped/saved automatically for you.

Application / Ionic

Then to be sure your background images inside your Application pages, you must ensure all views & lists your and uses the new directive sb-page-background

<ion-view sb-page-background>
    <ion-content>
        {{ "My Awesome Content!" |translate:"module_name" }}
    </ion-content>
</ion-view>

As the old directive sb-background-image is deprecated/empty it will not affect anymore your features.

Package

When you are done with your module, it's time to pack !

zip everything at the root of your module and you're done !

ModuleName.zip
├─ Controller
├─ controllers
│  └─[...]
├─ init.php
├─ features
├─ Model
├─ View
├─ resources
│  └─[...]
└─ package.json

Disable a Module

While in production modules can't be uninstalled, but can be disabled in Backoffice > Settings > Advanced > Modules