# Siberian developer documentation (full text) Source: https://github.com/Xtraball/SiberianCMS-Doc, published at https://developers.siberiancms.com/ When this text and the code on the trunk branch of https://github.com/Xtraball/Siberian disagree, the code is right. ================================================================ FILE: docs/agents.md ================================================================ # For AI agents This page is for coding agents and automation that read these docs to build or change a Siberian module, layout, template or integration. Humans are welcome too. ## Start here 1. Read [Getting started](documentation.md) for what Siberian is and how it is built. 2. Read [Modules](module.md) and [Basic usage](module/basic.md): structure, `package.json`, schema, `feature.json`, translations, packaging. 3. Open the **reference module** below and copy its shape instead of inventing one. 4. Check every signature you use against the Siberian code on `trunk`. **When the code and these docs disagree, the code wins**: fix the docs and open an issue. Machine readable entry points: | What | Where | |------|-------| | Index of every page, one line each | [llms.txt](/llms.txt) | | All pages in one plain text file | [llms-full.txt](/llms-full.txt) | | Raw Markdown of any page | `https://raw.githubusercontent.com/Xtraball/SiberianCMS-Doc/master/docs/.md` | | Siberian source | [github.com/Xtraball/Siberian](https://github.com/Xtraball/Siberian), branch `trunk` | ## Reference modules Start from the first one, then read the others when you need more. | Module | Use it for | Layout | |--------|-----------|--------| | **[module-skeleton](https://github.com/Xtraball/module-skeleton)**: start here | The smallest complete feature: one table, one editor form that saves, one mobile page that loads it, a `tools/pack.sh` that builds the zip. Copy it and rename it | Current: `init.php`, `features//feature.json` | | **Job**, in the core: `siberian/app/sae/modules/Job` on `trunk` | A full-sized feature that is always in sync with the core: several tables, forms, lists, an API, several mobile screens, `.po` translations | Current | | **[siberian-module-skeleton](https://github.com/Xtraball/siberian-module-skeleton)** | The Job module as a standalone package, with email templates and an uninstall script | Older: `bootstrap.php`, assets registered from `resources/var/apps`, `.csv` translations. It still installs, but do not copy its layout | For anything new, use the layout of `module-skeleton` and the Job module in the core, and the [Modules](module.md) page. ## Where each part of a feature lives Paths are relative to the module folder, in `module-skeleton` (the file names are the same in Job, with more files). | You are building | Read the docs | Look at | |------------------|---------------|---------| | Database tables and default data | [Modules: schema and data](module.md) | `resources/db/schema/module_skeleton.php`, `resources/db/data/` | | Models | [Modules](module.md) | `Model/Skeleton.php`, `Model/Db/Table/Skeleton.php` | | **Editor** page (the desktop screen where an app owner sets the feature up) | [Forms](module/forms.md), [Modules: layout files](module.md) | `controllers/ApplicationController.php`, `Form/Skeleton.php`, `resources/design/desktop/flat/layout/*.xml` and `.../template/.../edit.phtml` | | **Mobile** app screens | [Modules: feature.json](module.md), [Ionic tools](stack/ionic-tools.md), [Utilities](stack/utilities.md), [Offline mode](module/offline-mode.md) | `features/module_skeleton/feature.json`, `js/controllers/`, `js/factory/`, `assets/templates/l1/` | | JSON the app loads | [Modules](module.md) | `controllers/Mobile/ListController.php` | | Public API (for other systems) | [API](api.md) | `controllers/Api/` in the Job module | | **Backoffice** screens and menus | [Menus](module/hooks/menus.md), [Admin hooks](module/hooks/admin.md) | `siberian/app/sae/modules/Backoffice` in the core | | Reacting to events | [Hooks](module/hooks.md) | `init.php` | | Push notifications | [Push](module/push.md) | | | Customer account fields | [My account](module/account.md) | | | Texts and translations | [Translations in Modules](module.md) | `resources/translations/default/*.po` in the Job module | | Icons | [Icons](customization/icon.md) | `features//icons/` | The flow of `module-skeleton`, end to end: the editor form (`Form/Skeleton.php`) posts to `ApplicationController::saveAction`, which saves a row; the app route in `feature.json` opens the Angular controller, whose factory calls `ListController::loadContentAction` through `$pwaRequest`; the template shows the result. ## Rules that save you a rejected change - **Texts go in a `.po` catalog**, through `p__('', '...')` in PHP. When PHP prints a text inside a JavaScript string, use `p__js` instead (see [Utils](module/utils.md)). Never add a new `.csv` file. - **New behaviour is an option, off by default.** Do not change what existing installations do. - **Schema and data files must be idempotent.** They run again on update. An installed module is only updated when the `package.json` version is higher than the installed one, so bump it when you change a schema. - **LF line endings only.** - **The app JavaScript runs on Android and iOS.** Do not rely on a behaviour of only one platform. The minimum is Android 8 and iOS 15. - **After adding, renaming or deleting a design file**, rebuild the design cache with `php cli cache:design` (see [CLI](cli/siberian.md)), otherwise the file is never served. - **Do not edit files on a customer or production server.** Work in your module, package it, install it. - **Test against a real Siberian checkout** of `trunk`, not stubs. A feature is not done until it installs, shows in the editor and works in the app preview. ## Package and install 1. Build the zip with `./tools/pack.sh` (in `module-skeleton`), or zip the **contents** of the module folder yourself so that `package.json` is at the root of the zip. A "Download ZIP" from GitHub puts everything in a sub folder and will not install. 2. Upload the zip in the backoffice module installer (`Installer_Backoffice_ModuleController::uploadAction` in the core), or in a development tree copy the folder into `siberian/app/local/modules/`. 3. In `package.json`, set `dependencies.system.version` to the oldest Siberian your module supports (the docs assume 5.1.0 and later). ## When something is missing or wrong Open an issue on [github.com/Xtraball/Siberian/issues](https://github.com/Xtraball/Siberian/issues) with your Siberian version, what you did, what you expected and what happened. ================================================================ FILE: docs/documentation.md ================================================================ # Getting started ## What is Siberian Siberian is a self-hosted platform to build, manage and publish mobile applications. You install it on your own server, then create apps from a backoffice and an editor, and publish them as native **iOS** and **Android** apps and as a web app. An app is made of **features** (pages, forms, loyalty cards, push, catalogs...) arranged by a **layout** and dressed with a **template**. Everything on this site explains how to build, change or extend those pieces. Three editions share the same core, a module declares the minimum it needs: | Edition | Name | Use | |---------|------|-----| | SAE | Single App Edition | One app, for your own use | | MAE | Multi App Edition | Several apps, for agencies and freelancers | | PE | Platform Edition | A platform where your own customers create their apps | ## How it is built * **Server:** PHP (Zend Framework 1) with a MySQL/MariaDB database. Controllers, models, forms and the backoffice and editor screens live here. * **App:** an Ionic 1 / AngularJS web app running inside a Cordova shell (cordova-android 15, cordova-ios), so one code base produces the iOS app, the Android app and the web app. Apps support Android 8 and later, and iOS 15 and later. * **Modules:** a feature is a module with a PHP part for the server and an Ionic part for the app, packaged and installed as a zip. The exact versions of every framework and plugin are listed in [Frameworks](stack/frameworks.md). ## What a developer can do | I want to... | Start here | |--------------|------------| | Add a new feature to the apps | [Modules](module.md) and [Basic usage](module/basic.md) | | Add pages or fields to the editor | [Forms](module/forms.md), [Hooks](module/hooks.md) and [Menus](module/hooks/menus.md) | | Add fields to the customer account | [My account](module/account.md) | | Make a feature work without a connection | [Offline mode](module/offline-mode.md) | | Send push notifications | [Push notifications](module/push.md) | | Change how the home page looks | [Layouts](layout.md) | | Change the whole look of the apps | [Templates](customization/template.md) and [Icons](customization/icon.md) | | React to what happens (login, register, menus, app start) | [Hooks](module/hooks.md) | | Call Siberian from another system | [API](api.md) | | Script an installation | [Siberian CLI](cli/siberian.md) | | Use the app services (dialogs, pictures, location...) | [Ionic tools](stack/ionic-tools.md) and [Utilities](stack/utilities.md) | | Override or extend the core without editing it | [Core inheritance](module/core-inheritance.md) | | Report a problem | [Contribute](contribute.md) | ## Where to start 1. **Install Siberian** on a server that meets the [requirements](#requirements), following [Installation](#installation). 2. **Read how a module is structured**: [Modules](module.md), then [Basic usage](module/basic.md). 3. **Build a small feature** and test it in the editor and in the app preview. 4. **Come back to the other pages** (hooks, forms, API) when you need them. If you only want to use Siberian rather than extend it, you do not need this site: the backoffice and the editor are self-explanatory. # Requirements Siberian runs on a classic Linux web server. Check these four things before you install: the system, PHP, the database and a few command line tools. ## Server | Component | Requirement | Notes | |-----------|-------------|-------| | Operating system | **Linux** | Production servers only | | Web server | **Apache** or **Nginx** | Configuration examples in [Installation](#apache) | | OpenSSL | **1.0.1** or later | With TLS 1.2 support | | cURL | **HTTP/2** support | Check with `curl --version` | | Database | **MySQL** or **MariaDB 5.5+** | InnoDB or XtraDB engine | ## PHP | Setting | Requirement | Notes | |---------|-------------|-------| | Version | **8.2** minimum, **8.5** recommended | Supported range: `>=8.2 <8.6` | | Extensions | `SimpleXML`, `pdo_mysql`, `gd`, `mbstring`, `iconv`, `curl`, `openssl` | The installer checks them | | Functions | `exec()` | Must not be in `disable_functions` | | `allow_url_fopen` | `On` | | | `memory_limit` | `256M` or more | | | `post_max_size`, `upload_max_filesize` | **64M** to **128M** recommended | At least 32M, large updates need more | ## Command line tools | Tool | Status | Used for | |------|--------|----------| | `zip`, `unzip` | **Required** | Handling zip archives (packages, updates) | | `pngquant` or `optipng` | Optional | Smaller PNG images | | `jpegoptim` | Optional | Smaller JPEG images | ## Quick check Run these on the server to see where you stand: ```bash php -v php -m | grep -i -E "simplexml|pdo_mysql|gd|mbstring|iconv|curl|openssl" php -i | grep -E "allow_url_fopen|memory_limit|post_max_size|upload_max_filesize" openssl version curl --version | grep -i http2 zip -v | head -n 2 unzip -v | head -n 2 ``` Everything in place? Continue with the [installation](#installation). # Installation ## Configuration 1. First you will need to either checkout the project `git clone https://github.com/Xtraball/Siberian.git` or download the [zip archive](https://github.com/Xtraball/Siberian/archive/trunk.zip) then extract it on your webserver. 2. Setup your empty database and user 3. Configure your environment with [apache](#apache) or [nginx](#nginx) ### Apache If you are running under Apache, be sure that the directive `AllowOverride all` is working, unless the `.htaccess` configuration will fail. ``` ServerName [yourdomain.tld] CustomLog [/path/to/siberiancms]/var/log/httpd.access_log combined ErrorLog [/path/to/siberiancms]/var/log/httpd.error_log DirectoryIndex index.php DocumentRoot [/path/to/siberiancms] Options Indexes FollowSymLinks AllowOverride all ``` ### Nginx If you are running under Nginx, all you need is in the current configuration, please check the `fastcgi` options as they may vary depending on your installation ``` server { listen 80; root [/path/to/siberiancms]; access_log [/path/to/siberiancms]/var/log/nginx.access_log; error_log [/path/to/siberiancms]/var/log/nginx.error_log; index index.php index.html index.htm; server_name [yourdomain.tld]; location ~ ^/app/configs { deny all; } # Let's Encrypt configuration location = /.well-known/check { default_type "text/plain"; try_files $uri =404; } location ^~ /.well-known/acme-challenge/ { default_type "text/plain"; try_files $uri =404; } location / { try_files $uri /index.php?$query_string; } location ~ \.php$ { fastcgi_index index.php; fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_buffers 256 128k; fastcgi_connect_timeout 300s; fastcgi_send_timeout 300s; fastcgi_read_timeout 300s; } location ~* ^.+.(js|css|png|jpg|jpeg|gif|ico|html)$ { access_log off; log_not_found off; expires 0; } location ~ /\. { access_log off; log_not_found off; deny all; } gzip on; gzip_min_length 1000; gzip_proxied any; gzip_types text/plain application/xml text/css text/js application/x-javascript; client_max_body_size 128M; } ``` When you're done with the previous steps, reload your web server. Ensure `post_max_size` & `upload_max_filesize` are higher than 32M (64-128M recommended) at least, in case of large updates ## Web installer * Go to `http://yourdomain.tld` then follow the instructions ![welcome](img/install-sae.gif) ================================================================ FILE: docs/module.md ================================================================ # 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](https://github.com/Xtraball/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](https://github.com/Xtraball/Siberian/tree/trunk/siberian/app/sae/modules/Job) 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](https://github.com/Xtraball/siberian-module-skeleton) Modules are installed in the `app/local/modules` folder, for more information about the inheritance see this page [module/core-inheritance](module/core-inheritance) ## Structure ### Folder structure ```raw 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. ```json { "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 [ "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](#foreign-key-options) index|Array()|See the [Index options below](#index-options). 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: * If your module is about to install multiple features, split them into multiple files named with letters only, * ex: `features_core.php`, `features_extra.php`, etc... (not `feature1.php`, `feature2.php`) ### 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//.bundle.min.js`) and the `assets` folder is copied to the apps. ```json { "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](#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.* ```raw 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 {{ "Hello, I'm %s" | translate:"context_key" }} ``` ## 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 ```raw 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 ``` 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` * Use only the short path. ## 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. ```html
importBackground($option_value); ?>
``` 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** ```html {{ "My Awesome Content!" |translate:"module_name" }} ``` 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 ! ```raw 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** ================================================================ FILE: docs/module/basic.md ================================================================ # Basic usage ## Ionic code **Note: the current way is a `features//` folder declared by a `feature.json`, with its `js`, `scss`, `icons` and `assets/templates` (see [Module > feature.json](../module.md#featuresmodulenamefeaturejson), and `app/sae/modules/Job` for a real one). In the apps the templates are served from `features//assets/templates/`. The `var` folder described below is the legacy way, still supported through `init.php`.** Everything you need to organize, style & interact with your Feature in the native apps is located in the `var` folder Assets are merged on install & updates with the `init.php` see [register assets](#register-assets) You must place your files in a `modules/yourmodule` folder, here `modules/job`. ```raw ModuleName ├─ [...] ├─ resources │ └─ var │ └─ apps │ └─ modules │ └─ job │ ├─ styles │ │ └─ job.css │ ├─ controllers │ │ └─ job.js │ ├─ services │ │ └─ job.js │ ├─ factory │ │ └─ job.js │ ├─ directives │ │ └─ job.js │ ├─ img │ │ ├─ company.png │ │ └─ job.png │ └─ templates │ └─ l1 │ └─ view.html └─ [...] ``` ### Controller example ```raw App.config(function($stateProvider) { $stateProvider.state('job-view', { url: BASE_PATH+"/job/mobile_view/index/value_id/:value_id", controller: 'JobController', templateUrl: "modules/job/templates/l1/view.html" }); }).controller('JobController', function($rootScope[...] ``` The part `templateUrl: "modules/job/templates/l1/view.html"` concerns our template `app/local/modules/Job/resources/var/apps/modules/job/templates/l1/view.html`, we only keep the part of the path after `resources/var/apps/`. ## Bootstrap **Note: the `bootstrap.php` file is deprecated, see the Init section below for the new flavor. It is still loaded, but only when the module has no `init.php`: if both exist, `init.php` wins and `bootstrap.php` is not run.** Basically it's a file named `bootstrap.php` at the root of your package, below an example: ```php $cssBlock, "loadBlock" => $loadBlock, "featureBlock" => $featureBlock, "translationBlock" => $translationBlock, "manifestBlock" => $manifestBlock, ]; /** * Listener (generally in your init.php) */ Hook::listen( 'app.init.ready', 'Listening app init ready', function ($payload) { // Your stuff here! // Must return payload here return $payload; }, 0 ); ``` **Hook payload details** key|details ---|--- cssBlock|App specific pre-compiled CSS loadBlock|all the application settings featureBlock|all the app features translationBlock|array key/values with all the available translations for this app manifestBlock|webapp json manifest values, icon, name, start url --- #### mobile.controller.init Called when the mobile app controller is ready, and have Application, Session & Request elements! ```php [ 'hasChilds' => false, 'isVisible' => true, 'label' => __('Dashboard'), 'url' => $this->getUrl('backoffice'), 'icon' => 'fa fa-tachometer', ], 'invoices' => [ 'hasChilds' => false, 'isVisible' => $this->isPe(), 'label' => __('Invoices'), 'url' => $this->getUrl("sales/backoffice_invoice_list"), 'icon' => 'fa fa-ticket', ], 'manage' => [ 'hasChilds' => true, 'isVisible' => true, 'label' => __('Manage'), 'icon' => 'fa fa-users', 'childs' => [ 'users' => [ 'hasChilds' => true, 'isVisible' => true, 'label' => __('Users'), 'icon' => 'fa fa-users', 'childs' => [ 'users' => [ 'hasChilds' => false, 'isVisible' => true, 'label' => __('Users'), 'url' => $this->getUrl("admin/backoffice_list"), 'icon' => 'fa fa-users', ], ], ], [...], ]; ``` #### Payload details key|details ---|--- hasChilds|tells if the node is a parent or not isVisible|true, false or a condition to display the menu label|the translated title `__('Text to translate')` icon|same a previous with either `FontAwesome` or `IcoFont` url|the url to access the feature/module childs|if `hasChilds` is `true` then you must provide a childs array --- #### editor.header.menu.ready When the editor menu hierarchy is built, this action is triggered, you can then alter the tree as you want. This method is more complex than simply adding your menu, but it's way more powerful too. You callback function **must** return the given payload whether it's altered or not! ```php [ 'hasChilds' => false, 'isVisible' => true, 'label' => __('Dashboard'), 'id' => 'sb-tour-dashboard', 'is_current' => 'app_list' === $current, 'url' => $this->getUrl('/'), 'icon' => 'fa fa-tachometer', ], [...], 'profile' => [ 'hasChilds' => true, 'isVisible' => true, 'label' => __('Profile'), 'id' => 'sb-tour-profile', 'icon' => 'fa fa-user', 'childs' => [ 'my_account' => [ 'hasChilds' => false, 'isVisible' => true, 'label' => __('Account Settings'), 'url' => $this->getUrl('admin/account/edit'), 'is_current' => 'my_account' === $current, ], 'access_management' => [ 'hasChilds' => false, 'isVisible' => !$request->isWhiteLabelEditor() && $this->_canAccess('admin_access_management'), 'label' => __('Access Management'), 'url' => $this->getUrl('admin/access_management/list'), 'is_current' => 'access_management' === $current, ], [...] 'profile_divider' => [ 'isVisible' => true, 'divider' => true, 'is_current' => false, ], 'logout' => [ 'hasChilds' => false, 'isVisible' => true, 'label' => __('Log-out'), 'url' => $this->getUrl('admin/account/logout'), 'is_current' => false, ], ], ], [...] ]; ``` #### Payload details key|details ---|--- hasChilds|tells if the node is a parent or not isVisible|true, false or a condition to display the menu label|the translated title `__('Text to translate')` icon|same a previous with either `FontAwesome` or `IcoFont` is_current|highlight or not the current active menu divider|special item divider, not a menu id|the node id url|the url to access the feature/module childs|if `hasChilds` is `true` then you must provide a childs array --- --- #### editor.left.menu.ready When the sidebar left menu hierarchy is built, this action is triggered, you can then alter the tree as you want. This method is more complex than simply adding your menu, but it's way more powerful too. You callback function **must** return the given payload whether it's altered or not! ```php isAllowed($acl); } return true; }; // Fetch the current path $base = new \Core\Model\Base(); $currentUrl = str_replace($base->getBaseUrl(), "", $base->getCurrentUrl()); $payload["analytics"]["childs"]["customanalytics"] = [ "hasChilds" => false, "isVisible" => $canAccess("customer_analytics_acl"), // Ensure your are either FULL ADMIN, or have the Correct ACL!!! "label" => __("My custom analytics page"), "icon" => "fa fa-bar-chart", "url" => __path("/customanalytics/application/view"), // Generates a simple path to the feature "is_current" => (preg_match("#^/customanalytics/application/view#", $currentUrl)), // Check against what path should be to highlight or not ]; return $payload; }, 0 ); ``` **Hook payload details** ```php [ 'hasChilds' => true, 'isVisible' => $this->_canAccessAnyOf($editorAccess), 'label' => __('Editor'), 'id' => 'sidebar-left-group-editor', 'is_current' => false, 'url' => $this->getUrl('/'), 'icon' => 'fa fa-pencil', 'childs' => [ 'design' => [ 'hasChilds' => false, 'isVisible' => $this->_canAccess("editor_design"), 'label' => __('Design'), 'icon' => 'icon ion-sb-layout1', 'url' => $this->getUrl('application/customization_design_style/edit'), 'is_current' => ('editor_design' === $currentLink), ], 'colors' => [ 'hasChilds' => false, 'isVisible' => $this->_canAccess("editor_colors"), 'label' => __('Colors'), 'icon' => 'icon ion-sb-palette', 'url' => $this->getUrl('application/customization_design_colors/edit'), 'is_current' => ('editor_colors' === $currentLink), ], [...] ], ], 'users' => [ 'hasChilds' => false, 'isVisible' => $this->_canAccess("users"), 'label' => __('Users'), 'id' => 'sidebar-left-users', 'is_current' => false, 'url' => $this->getUrl('customer/application/list'), 'icon' => 'icon ion-sb-group', ], [...] 'settings' => [ 'hasChilds' => true, 'isVisible' => $this->_canAccessAnyOf(['editor_settings_tc', 'editor_settings_apis', 'editor_settings_domain', 'editor_settings_advanced', 'editor_settings_messages']), 'label' => __('Settings'), 'id' => 'sidebar-left-group-settings', 'is_current' => false, 'url' => $this->getUrl('/'), 'icon' => 'icon ion-sb-cogs', 'childs' => [ 'editor_settings_tc' => [ 'hasChilds' => false, 'isVisible' => $this->_canAccess("editor_settings_tc"), 'label' => __('Terms & Conditions'), 'icon' => 'fa fa-file-text', 'url' => $this->getUrl('application/settings_tc'), 'is_current' => ('editor_settings_tc' === $currentLink), ], [...] ], ], ]; ``` #### Payload details key|details ---|--- hasChilds|tells if the node is a parent or not isVisible|true, false or a condition to display the menu label|the translated title `__('Text to translate')` icon|same a previous with either `FontAwesome` or `IcoFont` is_current|highlight or not the current active menu id|the node id url|the url to access the feature/module childs|if `hasChilds` is `true` then you must provide a childs array --- ================================================================ FILE: docs/module/forms.md ================================================================ # Siberian Forms In Siberian we have implemented our own Forms & Elements to control design and javascript events Here is listed all these elements, how to use them from the `View` to the `Controller` **Form fields must respect the case of the field they represent in the database.** ## Form example Below is the code for a form using every single elements available, then each one being described ```php setAction(__path('/form/test')) ->setAttrib('id', 'form-test') ->addNav('form-test-nav'); /** Bind as a create form */ self::addClass('create', $this); /** Hidden */ $this->addSimpleHidden('element_hidden'); /** Image upload with crop */ $this->addSimpleImage( 'element_image', __('Image'), __('Import an image'), [ 'width' => 300, 'height' => 300 ] ); /** Input text */ $this->addSimpleText('element_text', __('Text')); /** Textarea */ $this->addSimpleTextarea('element_textarea', __('Textarea')); $full = $this->addSimpleTextarea('element_textarea_100', __('Textarea 100%')); $full->setNewDesignLarge(); /** Textara with CKEditor */ $richtext = $this->addSimpleTextarea( 'element_textarea_richtext', __('Textarea Richtext') ); $richtext->setRichtext(); $this->addSimpleSelect( 'element_select', __('Select'), ['Option #1', 'Option #2', 'Option #3', 'Option #4'] ); $this->addSimpleMultiSelect( 'element_multiselect', __('Multi Select'), ['Option #1', 'Option #2', 'Option #3', 'Option #4'] ); $this->addSimpleCheckbox('element_checbox', __('Checkbox')); $this->addSimpleMultiCheckbox( 'element_multichecbox', __('Multi Checkbox'), ['Option #1', 'Option #2', 'Option #3', 'Option #4'] ); $this->addSimpleRadio( 'element_radio', __('Radio'), ['Option #1', 'Option #2', 'Option #3', 'Option #4'] ); $this->addNav('submit-repeat', 'OK', false); } } ``` ### Form action The action to were the form submit its data is defined by `->setAction()` ```php setAction(__path('/form/test')) ``` ### Form default nav This will add on your form a default navigation, with back arrow & a submit button ```php addNav('form-test-nav') ``` #### parameters ```php addNav($name, $save_text = 'OK', $display_back_button = true) ``` |Parameter|Type|Description| |---|---|---| |$name|`String`|A unique name to identify your nav ControlGroup| |$save_text|`String`|The text used for the submit button| |$display_back_button|`Boolean`|Wether to display or not the back button, used to repeat submit on long forms without back button| ### Form binders Form events like `submit` or `change` are binded with css classes like below: #### > create Binds a form to submit data `onSubmit` event, add the css class `create` (or `edit`, which is bound the same way) ```php toggle Binds a form to submit data `onSubmit` event for single toggling forms, add the css class `toggle` see [#toggle-forms](module/forms#toggle-forms) ```php onchange Binds a form to submit data `onChange` event for every single item in the form, add the css class `onchange` see [#toggle-forms](module/forms#onchange-forms) ```php delete Binds a form to submit data `onSubmit` event for single row forms, add the css class `delete` see [#toggle-forms](module/forms#delete-row) ```php addNav('form-test-nav') ``` ![01-top-nav](../img/forms/01-top-nav.png) ### Image upload with crop #### code ```php addSimpleImage( 'element_image', __('Image'), __('Import an image'), [ 'width' => 300, 'height' => 300 ] ); ``` ![02-image-upload](../img/forms/02-image-upload.png) **Crop modal** ![02-image-crop](../img/forms/02-image-crop.png) ### Input text #### code ```php addSimpleText('element_text', __('Text')); ``` ![03-text](../img/forms/03-text.png) ### Input password #### code ```php addSimplePassword('element_password', __('Password')); ``` ![04-password](../img/forms/04-password.png) ### Textarea #### code ```php addSimpleTextarea('element_textarea', __('Textarea')); ``` ![05-textarea](../img/forms/05-textarea.png) ### Textarea 100% #### code ```php addSimpleTextarea('element_textarea_100', __('Textarea 100%')); $textarea_100->setNewDesignLarge(); ``` ![06-textarea-100](../img/forms/06-textarea-100.png) ### Richtext (CKEditor) #### code ```php addSimpleTextarea('element_textarea_richtext', __('Textarea Richtext')); $richtext->setRichtext(); ``` ![07-richtext](../img/forms/07-richtext.png) ### Select #### code ```php addSimpleSelect( 'element_select', __('Select'), array('Option #1', 'Option #2', 'Option #3', 'Option #4') ); ``` ![08-select](../img/forms/08-select.png) ### MultiSelect #### code ```php addSimpleMultiSelect( 'element_multiselect', __('Multi Select'), array('Option #1', 'Option #2', 'Option #3', 'Option #4') ); ``` ![09-multiselect](../img/forms/09-multiselect.png) ### Checkbox #### code ```php addSimpleCheckbox('element_checbox', __('Checkbox')); ``` ![10-checkbox](../img/forms/10-checkbox.png) ### MultiCheckbox #### code ```php addSimpleMultiCheckbox( 'element_multichecbox', __('Multi Checkbox'), array('Option #1', 'Option #2', 'Option #3', 'Option #4') ); ``` ![11-multicheckbox](../img/forms/11-multicheckbox.png) ### Radio button #### code ```php addSimpleRadio( 'element_radio', __('Radio'), array('Option #1', 'Option #2', 'Option #3', 'Option #4')); ``` ![12-radio](../img/forms/12-radio.png) ### Date, Datetime & Time pickers #### code ```php addSimpleDatetimepicker( 'date', __('Date Picker'), false, Siberian_Form_Abstract::DATEPICKER ); ``` ![datepicker](../img/forms/datepicker.png) ```php addSimpleDatetimepicker( 'datetimepicker', __('DateTime Picker'), false, Siberian_Form_Abstract::DATETIMEPICKER ); ``` ![datetimepicker](../img/forms/datetimepicker.png) ```php addSimpleDatetimepicker( 'timepicker', __('Time Picker'), false, Siberian_Form_Abstract::TIMEPICKER ); ``` ![timepicker](../img/forms/timepicker.png) ### Slider element #### code ```php addSimpleSlider( 'element_slidero', __('Slider'), array( 'min' => -100, 'max' => 100, 'step' => 5, 'unit' => '%' ), true); ``` The option `unit` goes along with the last parameter `$with_indicator` to show or not an indicator, `%`, `°`, `px`, etc... ![12-radio](../img/forms/14-slider.png) ### Submit repeat (for long forms) #### code ```php addNav('submit-repeat', 'OK', false); ``` ![13-repeat-submit](../img/forms/13-repeat-submit.png) ## Form usage Creating the form object ```php populate($siberian_object->getData()); ``` `->getData()` will return a raw key => values array representing the object.   Manually filled with default values: ```php getElement('element_textarea')->setValue('My textarea default value'); ``` `->getElement($name)` will return the form element identified by `$name`, you can then set value, options, attributes, etc... Displaying the form: ```php getRequest()->getPost(); /** Init the form */ $form = new Job_Form_Place(); /** Testing if the form is valid with the given values */ if($form->isValid($values)) { /** Do whatever you need when form is valid */ $place = new Job_Model_Place(); $place ->addData($values) ->addData(array( "is_active" => true, )) ; /** Moving the uploaded file to the app folder */ $path_banner = Siberian_Feature::moveUploadedFile( $this->getCurrentOptionValue(), Core_Model_Directory::getTmpDirectory()."/".$values['banner'] ); /** Replacing the tmp path, with the file path */ $place->setData("banner", $path_banner); /** When an image is not required, this parts handles the deletion */ if($values["icon"] == "_delete_") { $place->setData("icon", ""); } else if(file_exists(Core_Model_Directory::getBasePathTo("images/application".$values["icon"]))) { # Nothing changed, skip } else { $path_icon = Siberian_Feature::moveUploadedFile( $this->getCurrentOptionValue(), Core_Model_Directory::getTmpDirectory()."/".$values["icon"] ); $place->setData("icon", $path_icon); } /** An example of geocoding an Address */ if(!empty($values["location"])) { $coordinates = Siberian_Google_Geocoding::getLatLng(array( "address" => $values["location"] )); // Google requires an API key: pass it as 2nd argument ($apiKey) $place->setData("latitude", $coordinates[0]); $place->setData("longitude", $coordinates[1]); } /** Saving the object in */ $place->save(); $html = array( "success" => 1, "message" => __("Success."), ); } else { /** Do whatever you need when form is not valid */ $html = array( "error" => 1, "message" => $form->getTextErrors(), /** Required for javascript to handle errors */ "errors" => $form->getTextErrors(true), /** Required for javascript to handle errors */ ); } $this->_sendHtml($html); } ``` ### Basic toggling form & action ### Form toggle ```php setAction(__path("/job/place/togglepost")) ->setAttrib("id", "form-place-toggle") ; /** Bind as a delete form */ self::addClass("toggle", $this); $db = Zend_Db_Table::getDefaultAdapter(); $select = $db->select() ->from('job_place') ->where('job_place.place_id = :value') ; $place_id = $this->addSimpleHidden("place_id"); $place_id->addValidator("Db_RecordExists", true, $select); $place_id->setMinimalDecorator(); $this->addMiniSubmit(null, "", ""); $this->defaultToggle($this->mini_submit, "Enable place", "Disable place"); } } ``` Toggle form requires validators, and a "mini submit" like this: ```php addMiniSubmit( null, "", "" ); ``` And if you need a tooltip message to indicate the action: ```php defaultToggle( $this->mini_submit, "Enable place", "Disable place" ); ``` --- ```php getRequest()->getPost(); $form = new Job_Form_Place_Toggle(); if($form->isValid($values)) { $place = new Job_Model_Place(); /** Toggling the field */ $result = $place->find($values["place_id"])->toggle(); $html = array( "success" => 1, /** Return the new state for javascript to toggle the icon */ "state" => $result, /** Return the message corresponding to the new state */ "message" => ($result) ? __("Place enabled") : __("Place disabled"), ); } else { /** Do whatever you need when form is not valid */ $html = array( "error" => 1, "message" => $form->getTextErrors(), "errors" => $form->getTextErrors(true), ); } $this->_sendHtml($html); } ``` ### Basic delete form & action ### Form delete ```php setAction(__path("/job/place/deletepost")) ->setAttrib("id", "form-place-delete") ->setConfirmText("You are about to remove this Place !\n Are you sure ?"); ; /** Bind as a delete form */ self::addClass("delete", $this); $db = Zend_Db_Table::getDefaultAdapter(); $select = $db->select() ->from('job_place') ->where('job_place.place_id = :value') ; $place_id = $this->addSimpleHidden("place_id"); $place_id->addValidator("Db_RecordExists", true, $select); $place_id->setMinimalDecorator(); $mini_submit = $this->addMiniSubmit(); } } ``` ### Delete action ```php getRequest()->getPost(); $form = new Job_Form_Place_Delete(); if($form->isValid($values)) { $place = new Job_Model_Company(); $place->find($values["place_id"]); /** Delete the object */ $place->delete(); $html = array( 'success' => 1, 'success_message' => __('Place successfully deleted.'), ); } else { $html = array( "error" => 1, "message" => $form->getTextErrors(), "errors" => $form->getTextErrors(true), ); } $this->_sendHtml($html); } ``` ================================================================ FILE: docs/module/account.md ================================================================ # My account ## Account fields You can extend `My account` with more fields for your modules. *Important* `Account::addFields` must be used within the following hook from `init.php` ```php "spacer", "key" => "mymodule_spacer", ], [ "type" => "divider", "key" => "mymodule_divider", "label" => __("Divider #1"), ], [ "type" => "text", "key" => "mobile_number", "label" => __("Mobile number"), ], [ "type" => "textarea", "key" => "address", "rows" => "3", "label" => __("Address"), ], [ "type" => "number", "key" => "age", "min" => "1", "max" => "100", "step" => "1", "label" => __("Age"), ] ], "populateExtended", "saveExtended" ); return $payload; } ``` `$moduleKey` must be unique, `addFields` throws an exception if it is already used. Each field also gets a `_moduleKey` entry automatically. --- The different field types All types accept `label`; `is_required` shows a required marker. Type|Usage ---|--- spacer|This is a 20px blank spacer, simple. divider|This is a divider title, it uses `item item-divider item-divider-custom` classes text|A simple text input textarea|A textarea with configurable rows number|A number input with `min`, `max` & `step` settings select|A dropdown selector with `options: [["value" => "single", "label" => "Single"], ["value" => "married", "label" => "Married"]]` checkbox|A checkbox date|A date picker, with optional `date_format` and `date_skipdays` --- Callback examples The *$populateCallback* is called before generating the fields, it's used to populate them with the actual values and/or with defaults. Note: *$populateCallback* must be a pure function accessible from everywhere, it's a good practice to declare it inside `init.php` ```php (current application), * "request" => (current request), * "session" => (user session) * ] * @var $fields * [ * [ * "key" => "#Field_key#", * "value" => "#Field_value", // defaults empty * ], * [...] * ] */ $customer = $context["session"]->getCustomer(); foreach ($fields as &$field) { switch ($field["key"]) { case "mobile_number": $field["value"] = $customer->getMobileNumber(); break; case "address": $field["value"] = $customer->getAddress(); break; case "age": $field["value"] = $customer->getAge(); break; } } return $fields; } ``` The *$saveCallback* is called when the customer saves its account (before the customer itself is saved), with the submitted values of your fields. Note: *$saveCallback* must be a pure function accessible from everywhere, it's a good practice to declare it inside `init.php` ```php (current application), * "request" => (current request), * "session" => (user session) * ] * @var $fields * [ * [ * "key" => "#Field_key#", * "value" => "#Field_value", // defaults empty * ], * [...] * ] */ $customer = $context["session"]->getCustomer(); foreach ($fields as $field) { switch ($field["key"]) { case "mobile_number": $customer->setMobileNumber($field["value"]); break; case "address": $customer->setAddress($field["value"]); break; case "age": $customer->setAge($field["value"]); break; } } // Finally save the customer $customer->save(); return $fields; } ``` ================================================================ FILE: docs/module/push.md ================================================================ # Push notifications Push notifications are sent through OneSignal, with the `Push2\Model\Onesignal\Scheduler` class (module `Push2`). ## Scheduler ```php $this->getApplication()->getId(), // The application ID, required 'value_id' => $this->getId(), // The value ID, optional 'title' => "Message title", // Required 'body' => "Message body", // Required 'send_after' => null, // "Feb 13 2023 00:00:00 +01:00" date format for delayed push, null for immediate push 'delayed_option' => null, // "timezone" or null, timezone will try to send at the same time in the customer timezone 'delivery_time_of_day' => null, // "08:23" for 8:23 AM 'is_for_module' => false, // If true, the message is linked to a module, push will not be listed in the admin 'is_test' => false, // If true, the message is a test push, it will not be listed in the admin 'is_individual' => false, // If true, the message is individual, it works with player_ids, if false it will be sent to all customers 'open_feature' => false, // If true, the message will open a feature, it works with feature_id 'feature_id' => null, // The feature ID, required if open_feature is true 'player_ids' => null, // The player IDs, required if is_individual is true ]; $scheduler = new Scheduler($application); $scheduler->buildMessageFromValues($values); $scheduler->send(); ``` Other optional keys read by `Message::fromArray()`: `subtitle`, `big_picture`, `big_picture_url`, `open_url`, `feature_url`, `segment` (defaults to the app default segment, or "Subscribed Users"), and `use_location` with `latitude`, `longitude`, `radius`. `send()` returns the OneSignal response. `sendToTargets(AbstractTarget $targets)` sends to explicit targets instead of a segment, and `sendTest()` sends without persisting the message. ### Send to a single customer With the Individual push module, you can send a push to a single customer, this is done by using the `player_ids` parameter. ```php $this->getApplication()->getId(), // The application ID, required 'value_id' => $this->getId(), // The value ID, optional 'title' => "Message title", // Required 'body' => "Message body", // Required 'send_after' => null, 'delayed_option' => null, 'delivery_time_of_day' => null, 'is_for_module' => true, // If true, the message is linked to a module, push will not be listed in the admin 'is_test' => false, // If true, the message is a test push, it will not be listed in the admin 'open_feature' => false, // If true, the message will open a feature, it works with feature_id 'feature_id' => null, // The feature ID, required if open_feature is true ]; $scheduler = new Scheduler($application); $scheduler->buildMessageFromValues($values); $scheduler->sendToCustomer($customerId); // This part will automatically sets the player_id and is_individual to true ``` ================================================================ FILE: docs/module/offline-mode.md ================================================================ # Offline Mode # **Offline mode solely concerns mobile features modules.** If your feature doesn't require real time interaction with a server and can work without network access, the following guide will help you through the process of making your feature accessible in offline mode. ## Server side ## To begin with offline mode, you must tell Siberian if your module fully support offline mode or if the user experience will be downgraded. Partial support examples are : full content available but no interaction (comments or likes) in offline mode, venues information but no map view capability in offline mode, and such. Offline support must be declared inside the main model of your feature. This is the model set on the `model` attribute when you call `Siberian_Feature::install`. Example : **mymodule/resources/db/data/mymodule.php** ```php "MyModule_Model_MyModule", // ... ], ["code"]); ``` In this example our main model is `MyModule_Model_MyModule`. We need to override some functions inside the model to define the offline mode support range and what should be downloaded on client side at runtime. **mymodule/Model/MyModule.php** ```php "World"])`. The context is here to prevent mixup between modules & features. Default text is used as a key to be used in the translation system, and must be in english, however when building your module you can ship it with any translations you want, and we encourage you to do so ! ## Design Using the function `design_code()` you'll get the current theme, `flat` as of today (it is the only one). ## String manipulations ### cut `cut($string, $length, $suffix = "...", $strip_tags = true)` This function simply cuts any given `$string` to the desired `$length`, you can also specify a custom `$suffix` and the html tags are always stripped (the `$strip_tags` argument is ignored). ### ellipsis `ellipsis($string, $length, $ellipsis = "...")` This function shortens your `$string` to the desired `$length` by keeping its beginning and its end and replacing the middle with `$ellipsis` (characters can be changed). ### formatBytes `formatBytes($bytes, $precision = 2)` Convert `$bytes` to human readable strings, `$precision` can be adjusted. ### __js `__js($string, $escape = '"' [, mixed $args [, mixed $... ]])` This function is used to create safe JavaScript strings, the default character to escape is `"` but you can change it to `'` **Note:** this function also uses the translation function `__()` behind the scene. For a contextual string use `p__js($context, $string, $escape = '"' [, mixed $args ...])`. `__jss($string)` and `__jsd($string)` are shortcuts forcing `'` and `"`. ### __path `__path($path, $values, $locale)` This function allows to build URI relative path, you can also specify the `$locale` if you need to build a specific foreign page. Example: ```php echo __path("/job/application/edit", array("value_id" => 12, "option_id" => 42)); # output /job/application/edit/value_id/12/option_id/42 ``` ### __url `__url($path, $values, $locale)` This function allows to build fully qualified URI, you can also specify the `$locale` if you need to build a specific foreign page. Example: ```php echo __url("/job/application/edit", array("value_id" => 12, "option_id" => 42)); # output http://www.yourdomain.com/job/application/edit/value_id/12/option_id/42 ``` ### data_to_utf8 The function `data_to_utf8($array)` recursively walks against the given `$array` and try to detect/fix non-utf8 strings. --- ## Image manipulation ### PNG & JPEG Optimizers The class `Siberian_Media` provides a simple interface that will optimize/crush the given media in place. Usage `Siberian_Media::optimize($media_path, $force = false)` that's all. ================================================================ FILE: docs/module/security.md ================================================================ # Security ## Slack notifications If you want to receive the WAF alerts in Slack. Open the Firewall page of the Backoffice, the *Slack notification settings* section is always available (no `config.php` flag is needed). ![slack-notifications](../img/module/slack-notifications.png) Simply fill it with your Webhook URL, Channel & Username, tick *Enabled?* then save! Every *Firewall* alert will trigger a notification in your subsequent Slack channel! ================================================================ FILE: docs/module/core-inheritance.md ================================================================ # Core inheritance & customization In SiberianCMS we have designed an inheritance system to allow for custom modification & improvements. ## How it works ? Siberian ships as three editions, SAE, MAE & PE, and each one is a stack of folders in `app`: ```raw local > pe > mae > sae app// ``` | Edition | Layers loaded (lowest to highest) | |---------|-----------------------------------| | SAE | `sae`, `local` | | MAE | `sae`, `mae`, `local` | | PE | `sae`, `mae`, `pe`, `local` | A file with the same relative path in two layers is **replaced** by the upper one, it is not merged: any file named the same as in a lower layer takes precedence on it. `local` always wins, this is where your modules live. Be aware that the file you override may itself already be an override: on MAE and PE the file in `app/sae` can be hidden by a file of `app/mae` or `app/pe`, and then a copy of the `sae` one in `local` replaces the edition's version as well. Start from the file of the highest layer your edition has, not from the one in `app/sae`. ## Cache The core inheritance is cached: every design file of a layer is listed in `app//design/design-cache.json` (`app/local/design/design-cache.json` for your modules), and the layout only sends the files that are listed. So each time you add or remove a design file in local, the cache must be rebuilt, see [Module > Cache](../module.md#cache) (`./cli cache:design sae`, replace `sae` with your edition). **Note: when installing and/or updating a module with the regular zip package, the cache is automatically cleared for the users.** --- However you can also wipe various cache levels manually in the Backoffice dashboard: ![dashboard-cache](../img/module/dashboard-cache.png) ## Example ### Design files **If your module needs to override an actual Siberian design you must explicitely declare it in your module `init.php` (or the deprecated `bootstrap.php`)** ```php ` (see [Modules](../module)). 3. Add your test file in `tests/php-upgrade/regression/`. ## Running From the repository root: ```sh php tests/php-upgrade/run.php php tests/php-upgrade/run.php --fail-fast php tests/php-upgrade/run.php MyModule ``` The optional argument is a filter: only tests whose id (` :: `) contains it, case-insensitive, are run. The output ends with the PHP version, the passed and failed counts and the number of deprecations. The exit code is 1 when a test failed. Warnings and errors raised inside a test fail it. Deprecations are counted but do not fail. ## Writing a test A test file is named `Test.php`, in `tests/php-upgrade/regression/`, and returns an array of `'test name' => callable`. A test passes when the callable returns, and fails when it throws. ```php function () { t_equal('Hello', MyModule_Model_Helper::title(' Hello '), 'title'); }, 'module ships its init file' => function () use ($module) { t_true(is_file($module . '/init.php'), 'init.php'); }, ]; ``` Helpers from `bootstrap.php`: helper|details ---|--- `t_equal($expected, $actual, $label = '')`|strict (`!==`) comparison, throws with both values when they differ `t_true($actual, $label = '')`|casts to bool, then the same as `t_equal(true, ...)` `t_server()`|starts a local `php -S` server on `fixtures/router.php` and returns its base URL, for tests that need HTTP Load what you test with `require_once`, as the existing tests do (for example `Issue524ReinstallTest.php` loads a helper class of the Installer module). Only classes that do not need the database or the application registry can be loaded this way. Name a regression test after the issue it covers, and make sure it fails without the fix. ## PHP versions and lint - The core supports PHP `>=8.2 <8.6`. Run your tests on every supported version. - `tests/php-upgrade/Dockerfile.ci` builds a `php:-cli` image with the extensions needed (gd, pdo_mysql, mysqli, intl, bcmath, zip). `tests/php-upgrade/ci.sh ` builds it and runs the whole gate in Docker, for example `tests/php-upgrade/ci.sh 8.5`. - The gate (`fast-fail.sh`) runs `php -l` on first-party code, so a parse or fatal error fails it, and then the regression suite. Run `php -l` on your own files too. - Before a pull request, check your branch with `tests/php-upgrade/check-pr.sh [base-ref]`: it fails on CRLF line endings and on new `__()` / `p__()` texts missing from a `.po` catalog, and warns when source changed without a test. ================================================================ FILE: docs/module/hooks/system.md ================================================================ # System Hooks Hooks fired by the core when push messages are built, an SSL certificate changes or a cache is cleared. Register listeners in your `init.php`, and always return the payload (see [Hooks](../hooks)). #### push.message.ios.parsed Fired once the iOS (APNs) push message is built, right before the recipients are collected and the message is sent. You can alter the message (for example add custom data). ```php $message, "application" => $application, ]; /** * Listener (generally in your init.php) */ Hook::listen( 'push.message.ios.parsed', 'My module ios push', function ($payload) { $message = $payload['message']; // Alter the message here // Must return the payload, the core reads $payload['message'] afterwards return $payload; }, 0 ); ``` **Hook payload details** key|details ---|--- message|the `Apns2Message` about to be sent (the `aps` object is already filled) application|the `Application_Model_Application` the push is sent for --- #### push.message.android.parsed Fired once the Android (OneSignal) notification is built, with its sound, priority and targets (or location filter) set. You can alter the notification. ```php $notification, "application" => $application, ]; /** * Listener (generally in your init.php) */ Hook::listen( 'push.message.android.parsed', 'My module android push', function ($payload) { $notification = $payload['notification']; // Alter the notification here // Must return the payload, the core reads $payload['notification'] afterwards return $payload; }, 0 ); ``` **Hook payload details** key|details ---|--- notification|the `\onesignal\client\model\Notification` about to be sent application|the application the push is sent for Notes: - If a listener does not return the payload, the next listeners and the core get `null` and the push may fail, always return it. --- #### ssl.certificate.update Fired after an SSL certificate was saved from the back-office SSL settings (including when a Let's Encrypt certificate was generated successfully). Use it for example to reload a web server or copy the files. ```php $certificate, ]; /** * Listener (generally in your init.php) */ Hook::listen( 'ssl.certificate.update', 'My module ssl update', function ($payload) { $certificate = $payload['certificate']; // React to the new certificate here return $payload; }, 0 ); ``` **Hook payload details** key|details ---|--- certificate|the `System_Model_SslCertificates` record that was saved --- #### cache.clear.* Fired after the core cleared a folder of `var/`. There is one hook per target, the hooks are triggered without payload (listeners receive `null`). hook|fired after clearing ---|--- cache.clear.cache|`var/cache` cache.clear.cache_images|`var/cache_images` cache.clear.log|`var/log` (the `android_pwd` files are kept) cache.clear.locks|the `*.lock` files of `var/tmp` (all of them, or only the named ones) cache.clear.tmp|`var/tmp` ```php 800, "height" => 400, ); return $sizes; }); ``` `my_awesome_layout` is your unique layout code. `$position` is the position of the current feature, this way you can have different sizes for different positions in your layout. The callback also receives the layout options as an optional second argument (`function ($position, $options = null)`), the core `layout_siberian_swipe` and `layout_17` use it to change the size according to the chosen `icons` option. the function must return an array with `width` and `height` keys. ### Data This file creates & updates the entry in database and copy assets at installation time If you need to change options in your module while providing an update you must change the values here **Note:** you must never change `code` as this is the unique_code used to update your layout, otherwise this will create a new layout. ```php "border-right", "borders" => "border-left", "borders" => "border-top", "label" => "label-left", "textTransform" => "title-lowcase", )); # MyAwesomeLayout $datas = array( 'name' => 'MyAwesomeLayout', 'visibility' => \Application_Model_Layout_Homepage::VISIBILITY_HOMEPAGE, 'code' => 'my_awesome_layout', /** Preview is relative to [...]/resources/design/desktop/[flat|siberian]/images/ */ 'preview' => '/customization/layout/homepage/my_awesome_layout.png', 'use_more_button' => 1, 'use_horizontal_scroll' => 0, 'number_of_displayed_icons' => 8, 'position' => "bottom", "order" => 1200, "is_active" => 1, "use_subtitle" => 1, "use_homepage_slider" => 1, "options" => $default_options, ); # Same as the core layouts do: creates or updates the layout by its code, and its ACL resource \Siberian_Feature::installApplicationLayout($datas, "custom"); # Copying assets at installation time # same path as in `Siberian_Assets::registerAssets` \Siberian_Assets::copyAssets("/app/local/modules/MyAwesomeLayout/resources/var/apps/"); ``` |Field|Required ?|Description| |-----|---------------|-----------| |name|yes|This is your layout name, this can be `My Awesome Layout`| |visibility|yes|VISIBILITY_HOMEPAGE, VISIBILITY_ALWAYS, VISIBILITY_TOGGLE, VISIBILITY_FULLSCREEN| |code|yes|must be your layout_code, here `my_awesome_layout`| |preview|yes|This is the image used in Design > CHOOSE YOUR LAYOUT section| |use_more_button|yes|Does your layout works with a modal and so requires the more_button| |use_horizontal_scroll|yes|-| |number_of_displayed_icons|yes|The number of features used when using `use_more_button` this populates `features.overview.options`| |position|yes|Position of your menu: bottom, left, right| |order|yes|The order in the section Design > CHOOSE YOUR LAYOUT| |is_active|yes|must be 1| |use_subtitle|yes|Whether your layout uses subtitles. **/!\ Only available in flat design**| |use_homepage_slider|no|Whether your layout is designed to work with the homepageslider.| |options|no|Default options for your layout| |category_id|no|Layout category, `Siberian_Feature::installApplicationLayout($datas, "custom")` fills it from the category code (the core layouts use `default`)| |preview_new|no|Image of the layout in the modal (the core layouts set it, e.g. `/customization/layout/homepage/layout_swipe_modal.png`)| #### Visibility | | | | |------|------|------| |VISIBILITY_HOMEPAGE|The layout is displayed only on the homepage|![homepage](img/layout/homepage-1.png)| |VISIBILITY_ALWAYS|The layout is displayed everywhere and must take in consideration audio player controls & features UI|![always-1](img/layout/always-1.png)| |VISIBILITY_TOGGLE|Toggle layout side-menu|![toggle-1](img/layout/toggle-1.png)| |VISIBILITY_FULLSCREEN|The layout is displayed fullscreen|| ## Layout Structure The layout is composed of 3 or 4 files generally, `view.html`, `style.css`, `hooks.js` & if needed `modal.html` * `view.html` This is the main layout view. * `style.css` All the corresponding CSS for yout layout * `hooks.js` This file contains every hooks to tell Ionic what to load & how to arrange features * `modal.html` If you need to customize the modal this is the file you are searching for otherwise you can use one of the existing modal from Siberian. ### view.html ```html
  • {{ feature.name }}

    {{ feature.subtitle }}

``` Your homepage layout needs to be wrapped into a `
` with at least two classes first is `layout`, second is `my_awesome_layout`. --- Each feature is an item and needs some directives: |Element|Required ?|Description| |-------|---------------|-----------| |tabbar-items|yes|This directive binds the on-click of your element| |option="feature"|yes|Sets the option to the current feature| |go-to-url="goTo(feature);"|no|**Deprecated**, the `tabbar-items` directive opens the feature itself on click (`HomepageLayout.openFeature`), the attribute is still present in the core layouts| |ng-show="tabbar_is_visible"|yes|Condition used when the layout is always displayed| |ng-class="[...]"|yes|Used by the option in Editor > Colors > Homepage > Transparent to remove border & background|   Elements also use CSS classes to bind their colors upon the application defined color set |Class|Required ?| |-----|---------------| |item|yes| |homepage-custom|yes|   |Classic| |----------------| |![opaque](img/layout/opaque-1.png)![translucent-1](img/layout/translucent-1.png) ![translucent-2](img/layout/translucent-2.png)| ### style.css This CSS is the example given in the layout skeleton but with `my_awesome_layout` as namespace ```css .layout.my_awesome_layout { position: absolute; bottom: 5vh; width: 100%; } .layout.my_awesome_layout li.item { height: 28px; width: 150px; border-radius: 0 14px 14px 0; padding: 0; -o-transform: rotate(-7deg); -ms-transform: rotate(-7deg); -webkit-transform: rotate(-7deg); transform: rotate(-7deg); margin-left: -4vw; margin-top: 1px; border-width: 0px; } .layout.my_awesome_layout li.item img { float: left; height: 80%; margin: 2% 0 0 10%; } .layout.my_awesome_layout li.item h2 { float: left; } ``` ### hooks.js The hooks.js file is an Angular service, lazy loaded when using a custom Layout, allowing to manipulate features. ```js /** * * MyAwesomeLayout example * * All the following functions are required in order for the Layout to work */ angular.module("starter").service('my_awesome_layout', function ($rootScope, $timeout, HomepageLayout) { var service = {}; var _features; /** * Must return a valid template * * @returns {string} */ service.getTemplate = function() { return "modules/layout/home/my_awesome_layout/view.html"; }; /** * Must return a valid template * * @returns {string} */ service.getModalTemplate = function() { return "templates/home/l10/modal.html"; }; /** * onResize is used for css/js callbacks when orientation change */ service.onResize = function() { /** Do nothing for this particular one */ }; /** * Manipulate the features objects * * Examples: * - you can re-order features * - you can push/place the "more_button" * * @param features * @param more_button * @returns {*} */ service.features = function(features, more_button) { /** Keep a copy of the features in your hook */ _features = features; /** Place more button at the end */ features.overview.options.push(more_button); return features; }; return service; }); ``` |Function|Description| |--------|-----------| |service.getTemplate|Must return the path to the layout file| |service.getModalTemplate|Must return the path to a valid modal file| |service.onResize|Called when device orientation is changed or resized and everytime the homepage is shown| |service.features|Called to manipulate features| * Default modal templates are `templates/home/l10/modal.html` & `templates/home/modal/view.html`     #### Available objects in hook service `features` ```json "features": { "layoutCode": "my_awesome_layout", /** an array of every features in your application see [Option](#option) */ "options": [...] } ``` #### Chunk of features defined by the `limit` `features.overview` ```json "features.overview": { "layoutCode": "my_awesome_layout", /** an array of every features in your application see [Option](#option) */ "options": [...] } ``` #### Option Example of an option/feature for information ```json { "value_id": "6", "id": 6, "layout_id": "1", "code": "calendar", "name": "Calendar", "is_active": "1", "url": "http://www.yourdomain.com/57c7f8764a65a/event/mobile_list/index/value_id/6", "path": "/57c7f8764a65a/event/mobile_list/index/value_id/6", "icon_url": "http://www.yourdomain.com/var/cache/images/e9dcc367471a70a23261056b1129a198.png", "icon_is_colorable": "1", "is_locked": null, "is_link": false, "use_my_account": "0", "use_nickname": "0", "use_ranking": "0", "custom_fields": [], "position": "5", "is_visible": true } ``` #### Other `service.features = function(features, more_button)` examples In the example below, we are placing the `more_button` at the third place in the `features.overview.options` array. ```js [...] /** * Example with a layout containing 5 features (more_button include) & a modal */ service.features = function(features, more_button) { if(features.overview.options.length >= 4) { var third_option = features.overview.options[2]; var fourth_option = features.overview.options[3]; /** Placing more button at the third place (middle in layout) */ features.overview.options[2] = more_button; features.overview.options[3] = third_option; features.overview.options[4] = fourth_option; /** Removing 4 first option for the modal */ features.options = features.options.slice(4, features.options.length); } else { features.overview.options.push(more_button); } return features; }; [...] ```   The second example is used by the `Apartments` layout to create chunks of features. ```js [...] service.features = function(features, more_button) { _features = features; var more_options = features.options.slice(12); var chunks = new Array(); var i, j, temparray, chunk = 2; for (i = 0, j = more_options.length; i < j; i += chunk) { temparray = more_options.slice(i, i + chunk); chunks.push(temparray); } features.chunks = chunks; return features; }; [...] ``` ## Layout Options Layout options let you offer easy customization of your layouts to the final user with a simple form. If you want to add custom options to your layout you must create a Form and register it in your init. ```raw MyAwesomeLayout.zip └─ Form └─ MyAwesomeLayout.php ``` ### Init hook ```php # Register a custom form for the Layout Options Siberian_Feature::registerLayoutOptionsCallbacks("my_awesome_layout", "MyAwesomeLayout_Form_MyAwesomeLayout", function($datas) { $options = array(); return $options; }); ``` ### Form options Here is a simple example of what the form could look (from Layout 18). ![layout-18](img/layout/layout-18-form.png) And the example code ```php addSimpleSelect("label", __("Title position"), array( "label-left" => __("Left"), "label-right" => __("Right"), )); $textTransform = $this->addSimpleSelect("textTransform", __("Title case"), array( "title-lowcase" => __("Lower case"), "title-uppercase" => __("Upper case"), )); $borders = $this->addSimpleMultiCheckbox("borders", __("Display Borders"), array( "border-left" => __("Left"), "border-right" => __("Right"), "border-top" => __("Top"), "border-bottom" => __("Bottom"), )); $this->addNav("submit", __("Save"), false, false); self::addClass("btn-sm", $this->getDisplayGroup("submit")->getElement(__("Save"))); } } ``` Your form `MUST` extend `Siberian_Form_Options_Abstract` class. Then you can add your options. ### style.css And the view.html & style.css examples Here in the style.css we are using classes with the same names as the form values below, this is one way of doing it. ```css [...] .layout.layout_siberian_18 .label-right { text-align: right; } .layout.layout_siberian_18 .label-left { text-align: left; } .layout.layout_siberian_18 .title-lowcase { text-transform: inherit; } .layout.layout_siberian_18 .title-uppercase { text-transform: uppercase; } [...] ``` ### view.html And then in the view.html ```html ``` Here we are using a simple javascript "join" to concatenate all the selected options which have the same classes names. For the multi-select `{{ features.layoutOptions.borders.join(' ') }}` And for the simple selects we only echo the class, or classes `{{ features.layoutOptions.label+' '+features.layoutOptions.textTransform }}` ### Advanced usage of the options In the layout swiper, we are using the options to configure the Slider javascript object Here is an example of how we do it. in our hooks.js file, within the onResize function ```js /** * onResize is used for css/js callbacks when orientation change */ service.onResize = function() { var options = _features.layoutOptions; /** Do nothing for this particular one */ var time_out = ($rootScope.isOverview) ? 1000 : 200; $timeout(function() { if(swipe_instance != null && typeof swipe_instance.destroy == "function") { swipe_instance.destroy(true, false); } swipe_instance = new Swiper('.layout.layout_siberian_swipe .swiper-container', { direction: 'vertical', loop: (options.loop == "1") ? true : false, effect: 'coverflow', centeredSlides: true, initialSlide: (options.backcurrent == "1") ? service.last_index : 0, slidesPerView: 'auto', loopedSlides: 6, coverflow: { rotate: options.angle, stretch: options.stretch, depth: options.depth, modifier: 1, slideShadows : false } }); }, time_out); }; ``` previously we saved the `_features` array in the features function, and thus can use it. However, you can also access the layout options here `HomepageLayout.layoutOptions` ## Package When you are done with your layout, it's time to pack ! zip everything at the root of your layout and you're done ! If you have created your own module from the [skeleton](https://github.com/Xtraball/siberian-layout-skeleton), you can run `./tools.pack.sh` and voila ! ```raw MyAwesomeLayout.zip ├─ resources ├─ init.php └─ package.json ``` ================================================================ FILE: docs/customization/template.md ================================================================ # Template ** *Note: Template packages require Siberian 5.1.0 and above, otherwise they will not work* ** Demo template can be found [here](../img/template/template-corporate.zip)! ### What you need * A nice template idea ! **And to define its name:** * Your template name, like `TemplateAwesome`. note: always prefix it with `Template` this will avoid collision with modules! ## Structure of a template package Templates are installed in the `app/local/modules` folder   Below is the complete and minimal required structure for a template pack ```raw TemplateAwesome.zip ├─ resources │ ├─ db │ │ └─ data │ │ └─ template-awesome.php │ └─ images │ └─ templates │ └─ awesome │ └─ unified │ ├─ background.jpg // Must be square and 2732x2732px │ ├─ icon.jpg // Must be square and 512x512px │ └─ overview_new.jpg // Must be 640x568px └─ package.json ``` #### overview_new.jpg Example ![overview-image](../img/template/overview_new.jpg) ### package.json The `package.json` is used by the Installer to know the requirements, and routines to run during the installation/update process of your Template pack ```json { "name": "TemplateAwesome", "version": "2.0.0", "description": "Awesome template installer.", "dependencies": { "system": { "type": "SAE", "version": "5.1.0" /** 5.1.0 is required for the Template to work! */ } }, "type": "template" /** The type is important for Siberian to identify it as a template! */ } ``` |Field|Required ?|Description| |-----|---------------|-----------| |name|yes|Package name, avoid spaces and numbers| |description|yes|Package description| |type|yes|must be `template`| |version|yes|Your template pack version, for updates| |dependencies|yes|**version*:** 5.1.0 minimum, **type:** SAE/MAE/PE minimum installation type required| ** * version must be at least 5.1.0** ### Data `template-awesome.php` This file creates & updates the entry in database and copy assets at installation time If you need to change options in your module while providing an update you must change the values here ```php 'app/local/modules/TemplateAwesome/resources/media/library/awesome1.png', 'awesome2' => 'app/local/modules/TemplateAwesome/resources/media/library/awesome2.png', 'awesome3' => 'app/local/modules/TemplateAwesome/resources/media/library/awesome3.png', 'awesome4' => 'app/local/modules/TemplateAwesome/resources/media/library/awesome4.png', 'awesome5' => 'app/local/modules/TemplateAwesome/resources/media/library/awesome5.png', 'awesome6' => 'app/local/modules/TemplateAwesome/resources/media/library/awesome6.png', 'awesome7' => 'app/local/modules/TemplateAwesome/resources/media/library/awesome7.png', 'awesome8' => 'app/local/modules/TemplateAwesome/resources/media/library/awesome8.png', ]; // We insert icons one by one, so we can get the icon_id for each, and use it in our features! // We fill the array $libraryKeys with our "keys" so we can identify our icons later, to assign them to our features! $libraryKeys = []; foreach ($icons as $key => $icon) { $result = Feature::installIcons('TemplateAwesome', [$icon], false); $libraryKeys[$key] = $result; } // No changes here $ionicColors = [ 'header' => [ 'color' => '#ffffff', 'background_color' => '#5b5b5b' ], 'background' => [ 'background_color' => '#e2e2e2' ], 'homepage' => [ 'color' => '#5b5b5b', 'background_color' => '#ffffff', 'background_opacity' => '0', 'border_color' => '#ffffff', 'border_opacity' => '0', 'image_color' => '#5b5b5b' ], 'list_item_divider' => [ 'color' => '#ffffff', 'background_color' => '#5b5b5b' ], 'list_item' => [ 'color' => '#5b5b5b', 'background_color' => '#ffffff', 'border_color' => '#5b5b5b' ], 'card_item_divider' => [ 'color' => '#ffffff', 'background_color' => '#5b5b5b' ], 'card_item' => [ 'color' => '#5b5b5b', 'background_color' => '#ffffff' ], 'buttons' => [ 'color' => '#ffffff', 'background_color' => '#5b5b5b', 'border_color' => '#ffffff' ], 'checkbox_on' => [ 'color' => '#5b5b5b', 'background_color' => '#c1c1c1' ], 'checkbox_off' => [ 'background_color' => '#c1c1c1' ], 'checkbox_general' => [ 'color' => '#5b5b5b', 'background_color' => '#c1c1c1' ], 'radio' => [ 'color' => '#5b5b5b', 'background_color' => '#c1c1c1' ], 'toggle_on' => [ 'background_color' => '#c1c1c1' ], 'toggle_general' => [ 'color' => '#5b5b5b', 'background_color' => '#c1c1c1' ], 'toggle_off' => [ 'background_color' => '#c1c1c1', 'border_color' => '#808080' ], 'toggle_handle_on' => [ 'background_color' => '#c1c1c1' ], 'toggle_handle_off' => [ 'background_color' => '#5b5b5b' ], 'tooltip' => [ 'color' => '#ffffff', 'background_color' => '#5b5b5b' ], 'spinner_ios_text' => [ 'background_color' => '#5b5b5b' ], 'spinner_android_text' => [ 'background_color' => '#5b5b5b' ] ]; // No more keys, we will use "code" so we can have the same feature, many times $features = [ [ 'code' => 'custom_page', 'name' => 'About Us', 'icon_id' => $libraryKeys['awesome1']['icon_id'] ], [ 'code' => 'catalog', 'name' => 'Menu', 'icon_id' => $libraryKeys['awesome2']['icon_id'] ], [ 'code' => 'commerce', 'name' => 'Shop', 'icon_id' => $libraryKeys['awesome3']['icon_id'] ], [ 'code' => 'fanwall2', 'name' => 'Social wall', 'icon_id' => $libraryKeys['awesome4']['icon_id'] ], [ 'code' => 'form_v2', 'name' => 'Form', 'icon_id' => $libraryKeys['awesome5']['icon_id'] ], [ 'code' => 'folder_v2', 'name' => 'Folder #1', 'icon_id' => $libraryKeys['awesome6']['icon_id'] ], [ 'code' => 'folder_v2', 'name' => 'Folder #2', 'icon_id' => $libraryKeys['awesome7']['icon_id'] ], [ 'code' => 'tabbar_account', 'name' => 'My account', 'icon_id' => $libraryKeys['awesome8']['icon_id'] ] ]; Template::installOrUpdate( 'TemplateAwesome', 'awesome', 'awesome', 'layout_3', ['Design'], $ionicColors, $features ); ``` |Argument|Description| |--------|-----------| |1|Module (package) name, here `TemplateAwesome`| |2|Template name| |3|Template `code`, unique, also used in the image path `resources/images/templates//unified/`, **never change it** as it is the key used to update the template| |4|Layout code the template uses, if missing it falls back on `layout_6`| |5|Categories, an array of names, e.g. `['Design']`| |6|Ionic colors, see above| |7|Features, see above| |8|Position in the template list (optional, default `1000`)| Each feature accepts `code` (required, the feature must exist, otherwise it is skipped), `name`, `icon_id`, or `icon` (a path, when there is no `icon_id`), `background_image` and `colorized` (default `true`). ================================================================ FILE: docs/customization/icon.md ================================================================ # Icon ** *Note: icon packages require Siberian 5.1.0 and above, otherwise they will not work* ** ### What you need * Cool icons to share an awesome pack ! **And to define two names:** * Your icons pack name, like `MyIconPack`. ### Requirements * Icons must be 512x512, border to border, can be full white if you want them to be colorized, or colorfull ! ## Structure of an icon pack Icons are installed in the `app/local/modules` folder   Below is the complete and minimal required structure for an icon pack ```raw MyIconPack.zip ├─ resources │ ├─ db │ │ └─ data │ │ └─ my_icon_pack.php │ └─ media │ └─ library │ ├─ icon1.png │ ├─ icon2.png │ ├─ icon3.png │ ├─ icon4.png │ └─ [...] ├─ package.json └─ [...] ``` ### package.json The `package.json` is used by the Installer to know the requirements, and routines to run during the installation/update process of your icon pack ```json { "name": "MyIconPack", "description": "MyIconPack description", "type": "icons", "version": "1.0", "dependencies": { "system": { "type": "SAE", "version": "5.1.0" } } } ``` |Field|Required ?|Description| |-----|---------------|-----------| |name|yes|Package name, avoid spaces and numbers| |description|yes|Package description| |type|yes|must be `icons`| |version|yes|Your icon pack version, for updates| |dependencies|yes|**version*:** 5.1.0 minimum, **type:** SAE/MAE/PE minimum installation type required| ** * version must be at least 5.1.0** ### Data This file creates & updates the entry in database and copy assets at installation time If you need to change options in your module while providing an update you must change the values here **Note:** the icon pack is identified by its name (1st parameter of `installIcons`), and an icon by its path, installing again the same name and path updates instead of duplicating. ```php "...", "colorize" => true/false]` to override the 3rd parameter for one icon. It returns an array with `icon_id` (the first icon of the pack) and `library_id`, `icon_id` is used in the [Template](template) features. ## Package When you are done with your icon pack, it's time to zip ! zip `resources` and `package.json` and you're done ! You can also give a look at this icon pack [icon-pack-yappix](https://github.com/Xtraball/icon-pack-yappix). ```raw MyIconPack.zip ├─ resources └─ package.json ``` ================================================================ FILE: docs/stack/ionic-tools.md ================================================================ # Ionic Tools We've build our own services on top of Ionic to be more resilient when updating frameworks. # Dialog The `Dialog` service is used to handle `Alert`, `Prompt` & `Confirm` popups taking care of the stack, and extended with nice features. This service also takes care of the translations, every `String` goes into `$translate.instant(text)` And if `context` is defined, strings goes into `$translate.instant(text, context)`. Dialog methods always return a promise ## Alert ```js Dialog.alert(title, message, button, dismiss, context); ``` ![dialog-alert](../img/stack/dialogalert.png) #### options |Key|Type|Default|Usage| |---|---|---|---| |title|String|---|Dialog title| |message|String|---|Dialog message `text`| |button|String|---|Dialog button text, dismiss or ok| |dismiss|Integer|undefined|When defined, the Dialog will automatically dismiss after the given time in ms, if set to `-1` the dismiss time will be calculated automatically| |context|String|undefined|When defined, the context will be used to translate strings| ## Prompt ```js Dialog.prompt(title, message, type, value, buttons_array, css_class, context); Dialog.prompt(title, message, type, value, buttons_array, css_class, context) .then(function (result) { // the text typed by the user }); ``` ![dialog-prompt](../img/stack/dialogprompt.png) #### options |Key|Type|Default|Usage| |---|---|---|---| |title|String|---|Prompt title| |message|String|---|Prompt message `text`| |type|String|text|`text` or `password`, defaults to `text` if undefined| |value|String|undefined|Placeholder if type is set to `text`| |buttons_array|Array|`["OK", "CANCEL"]`|The two buttons: validate, then cancel| |css_class|String|undefined|CSS class used to customize render| |context|String|undefined|When defined, the context will be used to translate strings. It is the seventh argument: pass `undefined` for the ones you skip| ## Confirm ```js Dialog.confirm(title, message, buttons_array, css_class, context); Dialog.confirm(title, message, buttons_array, css_class, context) .then(function (result) { if (result) { // Confirmed } else { // Cancelled } }); ``` ![dialog-confirm](../img/stack/dialogconfirm.png) #### options |Key|Type|Default|Usage| |---|---|---|---| |title|String|---|Confirm title| |message|String|---|Confirm message `text`| |buttons_array|Array|---|`["YES","NO"]`| |css_class|String|undefined|CSS class used to customize render| |context|String|undefined|When defined, the context will be used to translate strings|     ---- # Modal This Modal service is using $ionicModal but will take cares of the stacking, the parameters & config are the same as the $ionicModal ones. The service also takes care of removing the Modal when closing via `modal.hidden` subscribers. The Modal service returns a promise with the modal object ## fromTemplateUrl ```js Modal.fromTemplateUrl(templateUrl, config); Modal.fromTemplateUrl(templateUrl, config) .then(function (modal) { // Do Whatever you want with your modal! }); ``` ## fromTemplate ```js Modal.fromTemplate(template, config); ```     ---- # Loader With this loader wrapper, you can only open the Loader once. Also the loader comes with a backdrop modal preventing any action! ## Show ```js Loader.show(text, config); ``` #### default ```js Loader.show() ``` ![loader-simple](../img/stack/loader-simple.png) #### example with text ```js Loader.show("Custom text...") ``` ![loader-custom](../img/stack/loader-custom.png) #### example with image ```js Loader.show("
") ``` ![loader-image](../img/stack/loader-image.png) ## Hide ```js Loader.hide(); ```     ---- # Location The location services is used to get the current position, with refresh rules to avoid GPS spam & abuse. ## getLocation #### usage ```js Location.getLocation(config, force) .then(function(position) { /** position: { coords: { latitude longitude altitude accuracy altitudeAccuracy heading speed } timestamp } */ }, function(error) { // action on error }) ``` Returns a promise If `force` is defined, whatever its value (`true` as well as `false`), the GPS request will be immediate. Leave it `undefined` to let the service decide: if the latest fetch is less than 42 seconds old, it returns the latest saved position within the promise then refreshes it in background. If the position cannot be found (permission denied, timeout, position unavailable), the promise is rejected and the service stops asking for the next calls, to avoid asking again and again.   #### default config ```js { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } ``` ## getLatest ```js Location.getLatest() .then(function(position) { /** position: { coords: { latitude longitude altitude accuracy altitudeAccuracy heading speed } timestamp } */ }, function(error) { // action on error }) ``` Returns a promise Get latest will return the latest position, without taking care of the freshness, if the position was never fetched, it will try to fetch it however.     ---- # Picture The picture service asks the user whether to take a new picture with the camera or to import one from the library (in the browser preview, it opens a file picker instead). It needs no permission from your module: the system picker is used to import a picture, and the camera permission is asked by the camera plugin when it is needed. ## takePicture ```js Picture.takePicture(width, height, quality) .then(function (response) { /** The image is returned as a data URI. response: { image: "data:image/jpeg;base64,/9j/4AAQ..." } */ }, function (error) { if (error && error.cancelled) { // The user backed out of the menu, of the camera or of the library. // Nothing went wrong and nothing was shown: do nothing. return; } // Any other error was already reported to the user with a dialog, // you only need to reset your own state here. }); ``` All three arguments are optional. #### options |Key|Type|Default|Usage| |---|---|---|---| |width|Integer|1200|Max width in pixels, from 1 to 10000. The picture keeps its proportions.| |height|Integer|1200|Max height in pixels, from 1 to 10000.| |quality|Integer|90|JPEG quality, from 0 to 100.| A value that is missing, or not a number, falls back to its default. A value out of range is brought back to the nearest limit. #### Good to know - `response.image` is always a complete data URI, whatever the version of the camera plugin in the app: the plugin returned the bare base64 up to version 7 and a data URI from version 8, and `Picture` accepts both. Use it as is, for example ``, and do not add `data:image/jpeg;base64,` yourself. - Handle the rejection even if you do nothing with it: a promise rejected by the user backing out is otherwise logged as "Possibly unhandled rejection". - Calling `takePicture` while the menu is already open rejects at once, without an error object. - The whole picture is kept in memory as text, so ask for a size that matches what you really need (the default 1200 px is enough for an avatar or a post), and send it to the server in one request.     ---- # SocialSharing ## share ```js SocialSharing.share(content, message, subject, link, file) ``` When using `SocialSharing.share()` with no parameters, the default message will be `Hi. I just found this in the MyAwesomeApp app.`   If only `content` is defined, `this` will be replace by `content`, like `Hi. I just found a new awesome music in the MyAwesomeApp app.`   When defining `message` it will completely override the default message including `content`, like `Just wanted to share this song with you guy's` but you can use `$1` as a placeholder for `content`, and `$2` as a placeholder for the Application name, like `Just wanted to share $1 with you guy's, in the App $2`   `link` will default to the Application download link   #### options |Key|Type|Default|Usage| |---|---|---|---| |content|String|undefined|Default placeholder text| |message|String|undefined|Message replacing the default one| |subject|String|undefined|Subject or your shared message| |link|String|undefined|Default to the Application download link| |file|String|undefined|A file url, an album cover or pdf for example|     ---- # Customer The customer service, is the only entry point you should use when dealing with user accounts, this in order to avoid any unwanted and/or duplicated behaviors. ## loginModal ```js Customer.loginModal(scope, login_callback, logout_callback, register_callback) ```   #### options |Key|Type|Default|Usage| |---|---|---|---| |scope|Object|undefined|If undefined, the scope will be $rootScope| |login_callback|Function|undefined|A function to be called on a sucessfull login| |logout_callback|Function|undefined|A function to be called on a sucessfull logout| |register_callback|Function|undefined|A function to be called on a sucessful registration|     ---- # $session The session provider contains the session id, the uid, and the screen resolution. This service is also used to store session data in localStorage. ## getId ```js $session.getId() ``` ## getDeviceUid ```js $session.getDeviceUid() ``` ## getDeviceScreen ```js $session.getDeviceScreen() /** { width, height, orientation } */ ``` ## setItem ```js $session.setItem(key, value) ``` ## getItem ```js $session.getItem(key) .then(function(value) { // the retrieved value, or null if it doesn't exists }); ``` ## removeItem ```js $session.removeItem(key) ``` ================================================================ FILE: docs/stack/utilities.md ================================================================ # Utilities ### isNotAvailableOffline The function below is used to prevent functions or features to work when Application is offline. This will return `true` or `false`, and when returning true, it also opens an alert. ```js $rootScope.isNotAvailableOffline(with_alert) ```   #### options |Key|Type|Default|Usage| |---|---|---|---| |with_alert|Boolean|true|Opens an alert by default|   ### isNotAvailableInOverview The function below is used to prevent some functions to work inside the Application overview, when editing. This will return `true` or `false`, and when returning true, it also opens an alert. ```js $rootScope.isNotAvailableInOverview(with_alert) ```   #### options |Key|Type|Default|Usage| |---|---|---|---| |with_alert|Boolean|true|Opens an alert by default|   ### isNativeApp This variable is used to differentiate native Apps Android & iOS from Browser App or Progressive Web App. ```js $rootScope.isNativeApp ``` ================================================================ FILE: docs/stack/frameworks.md ================================================================ # Frameworks Below you'll find a complete list of the various frameworks we use both in Editor, Backoffice & Applications. Most of our Cordova & Ionic libraries are customized for Siberian. ## Editor The main codebase inside the editor is powered with `jQuery` and `bootstrap`. ### FontAwesome - Version: `4.7.0` - [See on Github](https://github.com/FortAwesome/Font-Awesome) ### Bootstrap - Version: `3.2.0` - [See on Github](https://github.com/twbs/bootstrap) ### CKEditor - Version: `4.13.1` - CKEditor our richtext editor, used all across the Editor for every feature. - [See on Github](https://github.com/ckeditor) ### CodeMirror - Version: `latest` - CodeMirror is used to highlight Code, Scripts & more, we use it for our inline `SCSS` editor & also as a plugin for our CKEditor source feature. - [See on Github](https://github.com/codemirror/CodeMirror) ### Chart.js - Chart.js is powering our App analytics like below. - [See on Github](https://github.com/chartjs/Chart.js) ![chart-js](../img/stack/chartjs.png) ### DataTables - [See on Github](https://github.com/DataTables/DataTables) ### nestedSortable - [See on Github](https://github.com/ilikenwf/nestedSortable) ### prettyPhoto - [See on Github](https://github.com/scaron/prettyphoto) ### jQuery UI - [See on Github](https://github.com/jquery/jquery-ui) ### SweetAlert - Version: `latest` - [See on Github](https://github.com/t4t5/sweetalert) ![sweet-alert](../img/stack/sweetalert.png) *Basic example* ```js swal("Here's a message!"); ``` You can see [this page](http://t4t5.github.io/sweetalert/) for more advanced usage. ## Backoffice In the backoffice we are mainly using AngularJS along with some plugins for UI, CKEditor, Graphs. ### FontAwesome - Version: `4.2.0` - [See on Github](https://github.com/FortAwesome/Font-Awesome) ### Bootstrap - Version: `3.2.0` - [See on Github](https://github.com/twbs/bootstrap) ### AngularJS - Version: `1.3.0` - [See on Github](https://github.com/angular/angular) ### CKEditor - Version: `4.4.6` - [See on Github](https://github.com/ckeditor) ## Backend All our backend is running under Zend Framework with a complete custom stack on top of it, we also use libraries for Push & SCSS. ### PHP - Version: from `8.2` up to `8.5` (`>=8.2 <8.6`), `8.5` recommended - [See on Github](https://github.com/php) ### Zend Framework - Based on ZF1, customized for Siberian (Zend Framework `1.12.3`) & topped with Siberian library - [See on Github](https://github.com/zendframework/zf1) ## Applications ### Based on Ionic / AngularJS - Ionic `1.3.5` (Ionic 1, not Ionic 2+), AngularJS `1.5.3` #### Highly customized with - Lazy & Modular loading - Dynamic caching - Extended SCSS   ### Cordova #### All our cordova platforms are forked and customized. #### Android - Minimum Android 8.0 (API 26), `cordova-android` 15.1.0 - [See on Github](https://github.com/Xtraball/cordova-android) #### iOS - Minimum iOS 15, `cordova-ios` 6.1.1 (CordovaLib), the iOS shells are built with Xcode 26 in CI - [See on Github](https://github.com/Xtraball/cordova-ios) #### Browser - [See on Github](https://github.com/Xtraball/cordova-browser)   ### Cordova Plugins #### All our cordova plugins are forked and customized. The list below follows `submodules.json`, each plugin lives in its own repository under `https://github.com/Xtraball/`, on the `siberian` branch. |Plugin|Usage| |------|-----| |[AdmobPro](https://github.com/Xtraball/AdmobPro)|Google AdMob ads Banner & Interstitial| |[AppVersion](https://github.com/Xtraball/AppVersion)|| |[Badge](https://github.com/Xtraball/Badge)|| |[BarcodeScanner](https://github.com/Xtraball/BarcodeScanner)|QRCode scanner| |[BrowserTab](https://github.com/Xtraball/BrowserTab)|| |[Camera](https://github.com/Xtraball/Camera)|Camera & Library manager| |[Clipboard](https://github.com/Xtraball/Clipboard)|| |[Device](https://github.com/Xtraball/Device)|| |[File](https://github.com/Xtraball/File)|| |[Geofence](https://github.com/Xtraball/Geofence)|| |[Geolocation](https://github.com/Xtraball/Geolocation)|| |[InAppBrowser](https://github.com/Xtraball/InAppBrowser)|| |[Insomnia](https://github.com/Xtraball/Insomnia)|Prevents sleep for continuous app usage like GPS navigation| |[IonicWebview](https://github.com/Xtraball/IonicWebview)|Web view of the Android shell| |[Keyboard](https://github.com/Xtraball/Keyboard)|| |[LocalNotifications](https://github.com/Xtraball/LocalNotifications)|| |[MediaNative](https://github.com/Xtraball/MediaNative)|| |[MusicControls](https://github.com/Xtraball/MusicControls)|Display music controls in Notification center| |[Navigator](https://github.com/Xtraball/Navigator)|| |[Onesignal](https://github.com/Xtraball/Onesignal)|Push notifications| |[Permissions](https://github.com/Xtraball/Permissions)|Android runtime permissions (a dummy JavaScript file on iOS)| |[Promises](https://github.com/Xtraball/Promises)|| |[PluginSiberian](https://github.com/Xtraball/PluginSiberian)|| |[SocialSharing](https://github.com/Xtraball/SocialSharing)|| |[SplashScreen](https://github.com/Xtraball/SplashScreen)|| |[StatusBar](https://github.com/Xtraball/StatusBar)|| |[WebView](https://github.com/Xtraball/WebView)|| |[Whitelist](https://github.com/Xtraball/Whitelist)|| The iOS shell uses `IosXhr` instead of `IonicWebview`, see [docs/mobile-parity.md](https://github.com/Xtraball/Siberian/blob/trunk/docs/mobile-parity.md) in the Siberian repository for the differences between the Android and iOS plugin versions.   ### Javascript #### Swiper - Version: `3.4.2` - [See on Github](https://github.com/nolimits4web/Swiper)   #### Lodash - Version: `4.13.1` - [See on Github](https://github.com/lodash/lodash)   #### LocalForage - Version: `1.4.3` - [See on Github](https://github.com/localForage/localForage)   #### Siberian NqQueue - [See on Github](https://github.com/Xtraball/angular-queue)   #### Moment.js - Version: `2.18.1` - [See on Github](https://github.com/moment/moment)   #### ProgressBar - Version: `1.0.1` - [See on Github](https://github.com/kimmobrunfeldt/progressbar.js)   ================================================================ FILE: docs/cli/siberian.md ================================================================ # Siberian CLI Documentation ## Introduction Siberian ships with a CLI to help debugging & troubleshooting. Run it from the `siberian` folder with `./cli` (or `php cli`). ## CLI available methods |Method|Min. version|Params|Description| |------|------------|------|-----------| |export:schemas|5.1.0|-|Exports the current schema tables (only schemas) to `var/schema/CURRENT_VERSION/TABLE.php` this is useful to prepare db files for your custom modules| |export:database|5.1.0|-|Exports the current mysql database| |version|5.1.0|`version`|Updates `lib/Siberian/Version.php` with the given version| |user:create|5.1.0|-|This action will ask for an e-mail and a password to create a new backoffice user| |user:update-password|5.1.0|-|This action will ask for an e-mail and a password, usefull to change a lost backoffice password| |dev / development|5.1.0|-|Fast switch in development mode| |prod / production|5.1.0|-|Fast switch in production mode| |cache:design|5.1.0|`sae`, `mae`, `pe`|Rebuilds SAE/MAE/PE cache for design files| |cache:clear|5.1.0|-|Clear `var/cache`, `var/log` & `var/tmp`| |license:set|5.1.0|`license`|Set the product license from cli| |i18n:extract|5.1.0|`[module] [context]`|Extracts all missing translations| |app:manifest|5.1.0|`--skip-cache`|Rebuilds the app manifest| |app:keystore:restore|5.1.0|`appId`|Extracts a keystore from the database| |info / infos|-|-|Prints generic information about the current installation| |password|-|`[length]`, `--no-uppercase`, `--no-numeric`, `--no-special`|Generates a strong password (24 characters by default)| |https|-|`on`, `off`|Switch the soft HTTP to HTTPS redirection on or off| |test:http2|-|-|Tests whether HTTP/2 is available in php-curl for the cli and cron| |module:version:get|-|-|Lists the installed modules and their versions| |module:version:set|-|`module`, `version`|Sets the version of an installed module| |module:quarantine|-|`list`, `recheck [Module]`, `release Module`, `scan`|Lists, rechecks, releases or scans the modules of `app/local/modules` put in quarantine| |build:queue:flush|-|-|Flushes the source and APK build queues and removes the locks| |security:scan|-|`filePath`|Scans the given file with ClamAV (if configured in the Backoffice)| |subscription:check-payments|-|-|Manually runs the payment verification for expired subscriptions (PE, normally run by the cron)| |subscription:sync|-|-|Manually caches the payment statuses| |google:test-api-key|-|`apiKey`|Tests a Google Maps API key| |waf:status / waf:enable / waf:disable|-|-|Gets, enables or disables the Web Application Firewall| |waf:extension:list|-|-|Lists the extensions allowed by the Web Application Firewall| |waf:extension:allow / waf:extension:disallow|-|`extension`|Allows or disallows an extension in the Web Application Firewall| |help|5.1.0|-|Display the command help manual| ### Examples ```bash prompt $ ./cli export-schemas prompt $ ./cli export-database Filename for the export: dump.sql Export done. prompt $ ./cli version 5.1.13 prompt $ ./cli user:create Input new backoffice user e-mail: demo@demo.com Input new backoffice user password: ******** Your new user is now created. prompt $ ./cli user:update-password Input backoffice user e-mail to change: demo@demo.com Your new password (min 9 characters, 1 uppercase letter, 1 lowercase, 1 number & 1 special character): ******** Password successfully changed. prompt $ ./cli dev prompt $ ./cli prod prompt $ ./cli cache:design prompt $ ./cli cache:clear prompt $ ./cli app:manifest prompt $ ./cli help ``` ================================================================ FILE: docs/api.md ================================================================ # SiberianCMS API Documentation ## Introduction Welcome to the SiberianCMS REST API documentation. This doc contains reference materials to help you implementing the API into your system. SiberianCMS API provides you with the ability to manage your users, as well as your applications. You can find our PHP Library: * [Siberian API php](https://github.com/Xtraball/siberiancms-api-php). ## At a glance * The API is implemented with REST and is secured by an HTTP Basic Authentication or a Bearer Token. * The requests are made over HTTP POST and the responses are delivered in JSON. * For each request, you need to post all of your data together with your credentials previously created from your SiberianCMS's back-office. ## Create your API User * Open your Siberian Backoffice, then go in *Manage > Users > API* * Create your user & select the API Accesses to grant him. * For the HTTP Basic Auth, simply uses your username & password. * For the Bearer Token, get the generated Token and send it to the API with the header below ```raw // use this custom header, we do not use the regular Authorization HTTP header due to some restrictions. Api-Auth-Bearer: Bearer YOURTOKEN If your token is: 965327d7caa3d748bb70630b8f1ec64b7f0882ec so your header will be Api-Auth-Bearer: Bearer 965327d7caa3d748bb70630b8f1ec64b7f0882ec ``` ## Basic usage for the PHP library #### Init the API with Basic Auth or Bearer token ```php isSuccess()) { $user_id = $response->getResponse("user_id"); $token = $response->getResponse("token"); } else { echo $response->getErrorMessage(); } ``` #### Create an application ```php isSuccess()) { $app_id = $response->getResponse("app_id"); $app_url = $response->getResponse("app_url"); } else { echo $response->getErrorMessage(); } ``` Other actions are documented here [API Documentation](http://developer.siberiancms.com/api/) #### Run test (on you development instance) Basic Auth ```bash ./run-test.sh siberianurl basic username password ``` Bearer token ```bash ./run-test.sh siberianurl bearer token ``` ## APIs Here is the list of the available APIs: ### Application * [Create an application](./application#create) * [Update an existing application](./application#update) * [Grant access to an application](./application#grant-user) * [Revoke access to an application](./application#revoke-user) ### User * [Create a user](./user#create) * [Update an existing user](./user#update) * [Check whether a user exists](./user#exists) * [Authenticate a user](./user#authentication) * [Send a new password to a user](./user#forgot-password) ### Backoffice * [Rebuild manifest](./backoffice#manifest) * [Clear tmp](./backoffice#clear-tmp) * [Clear cache](./backoffice#clear-cache) * [Clear logs](./backoffice#clear-logs) ### Push notifications The global push API was removed in 5.0.0, see [Push](./push). ### Local API access !!! warning When enabled, **every request made from the server itself skips the API authentication**, no user, password or token is needed and all API privileges are granted. Leave it disabled unless you really need it. The setting `allow_local_api` is read in the API base controller (`Api_Controller_Default::init()`). It is **off by default** (a missing setting counts as disabled), and only the exact value `true` enables it. When it is enabled, a request skips the Basic and Bearer authentication and the ACL check if the client address equals the server address (`$_SERVER['REMOTE_ADDR'] === $_SERVER['SERVER_ADDR']`), that is a call made from the same machine, typically a script or cron job running `curl` against the instance's own address. To enable it, add this line to `config.user.php` (next to `config.php`, in the `siberian` folder; `config.php` loads it): ```php