Combu Addons

The version 2.1 of Combu brought the new cool feature of add-ons support, for example License Manager. The really cool thing is that you can create your own add-ons and share them with other users of Combu or even sell them yourself, we will not ask for royalties or any other cost from the income that you would earn from your hand-crafted add-ons.

Of course creating a server-side add-on requires deep PHP knowledge, but there’s tons of tutorials around the web about PHP programming and for an experienced developer it shouldn’t be hard to learn and code in this language.


Install and uninstall the add-ons

The add-ons must be placed in the folder /addons of your Combu server installation, an add-on usually consists of a folder with a file addon.php and eventually other files. If you have 2 add-ons with the same folder name then you will have to rename one of them and make proper changes to their configuration or eventually source code. You don’t need to do anything else, Combu API will take care to automatically loop through any sub-folder of /addons and load the valid add-ons found.

To uninstall an add-on you only have to delete its folder from /addons


Create your add-ons

The add-ons are handled by the class AddonModule and any add-on is an object of this class, the folder /addons is automatically scanned by Combu API in AddonModule::LoadAddons(). The first step to create a new add-on is to create a new folder in /addons, you should choose a proper name for your add-on so that it’s almost unique, and create a new file addon.php in the folder (all any other extra file used by your add-on must be in this folder or subfolders).

The file addon.php is the loader of your add-on, so it’s here that you will have to write your initialization code: since it is run by AddonModule, you will have automatically access to a variable called $addon of type AddonModule that you can configure at your needs.

The first step is to give a name to your module:

$addon->Name = "My awesome add-on";

If your add-on will have pages to be displayed in the administration console:

// The 1st parameter is the display text
// The 2nd is the file URL relative to the add-on folder
$addon->AddAdminMenu("Display menu", "your_admin_file.php");

If your add-on needs to be notified when an account is created:

function MyAwesomeAddon_OnUserCreate ($user) {
   // do something, $user is of type CB_Account
// Register the handle
$addon->OnUserCreate = "MyAwesomeAddon_OnUserCreate";

If your add-on needs to be notified when an account is updated:

function MyAwesomeAddon_OnUserUpdate ($user) {
   // do something, $user is of type CB_Account
// Register the handler
$addon->OnUserUpdate = "MyAwesomeAddon_OnUserUpdate";

If your add-on needs to be notified when an account is deleted:

function MyAwesomeAddon_OnUserDelete ($user) {
   // do something, $user is of type CB_Account
// Register the handler
$addon->OnUserDelete = "MyAwesomeAddon_OnUserDelete";

If your add-on needs to edit the output user data that is sent to the clients:

function MyAwesomeAddon_OnUserCustomDataOutput ($user, &$customData) {
   // do something, $user is of type CB_Account and $customData is the associative key/value array of the custom data
   $customData["NewKey"] = "NewValue"; // you can add/edit data to output
   unset($customData["MyHiddenKey"]); // or you can delete data from output
// Register the handler
$addon->OnUserCustomDataOutput = "MyAwesomeAddon_OnUserCustomDataOutput";

If your add-on needs to deny an account custom data from being updated by clients:



Create a data class

If your add-on requires new classes on server and save their instances on database, then the new class must inherit from DataClass (we suggest to write each class in its own file in the folder of your add-on and include the files at top of addon.php):

namespace Combu;

class MyNewClass1 extends DataClass {
    public $Id = 0; // usually a BIGINT with AUTOINCREMENT as PRIMARY KEY
    public $IdApp = 0; // usually want to filter out the records by App
    public $MyProperty1 = 0;
    public $MyProperty2 = "";

    // Contructor
    public function __construct($src = null, $stripSlashes = false) {
        global $Database;
        if ($src == null)
        if (is_array($src)) {
            // Load by array
            $this->_loadByRow($src, $stripSlashes);
        } else if (is_numeric($src) && intval($src) > 0) {
            // Load by Id
            $this->_loadFilter(self::GetTableName(__CLASS__), "Id = " . intval($src));

     * Get the records on database
     * @param int $idApp Filter by AppId
     * @param int $limit Max number of results (for paged results)
     * @param int $offset Offset number of results (for paged results)
     * @param int $count Will be set to the total count of results (unfiltered)
     * @param boolean $returnArray If TRUE then it will return associative arrays else objects
     * @return array Returns the array of records
    public static function Load ($idApp = 0, $limit = null, $offset = null, &$count = null, $returnArray = false) {
        $where = "";
        $orderBy = "MyProperty1 DESC";
        if ($idApp > 0) {
            $where = sprintf("(IdApp = %d)", $idApp);
        return self::_load(self::GetTableName(__CLASS__), ($returnArray ? "" : __CLASS__), $where, $orderBy, $limit, $offset, $count);

     * Save the record in the database
     * @return bool Returns TRUE on success
    public function Save() {
        global $Database;
        if ($this->Id > 0) {
            $query = sprintf("UPDATE %s SET MyProperty1 = %d, MyProperty2 = '%s' WHERE Id = %d",
        } else {
            $this->PublishDate = Utils::GetCurrentDateTimeFormat();
            $query = sprintf("INSERT INTO %s (IdApp, MyProperty1, MyProperty2) VALUES (%d, %d, '%s')",
        if ($Database->Query($query)) {
            if ($this->Id < 1) { $this->Id = $Database->InsertedId();
            return TRUE;
        return FALSE;

     * Delete the record from the database
     * @return bool Returns TRUE on success
    public function Delete() {
        if ($this->Id > 0) {
            return $this->_Delete(self::GetTableName(__CLASS__), "Id = " . $this->Id);
        return FALSE;



Create your own web services

Creating a custom add-on (even if it’s blank) is definitely the best way to add your own web services to handle things server-side, you only need to create a PHP script in your add-on folder in which you would include the basic API autoloader:


include_once '../../lib/api.php';

use Combu\MyNewClass1;
use Combu\Utils;

if (isset($WS_REQUEST["action"])) {
	switch ($WS_REQUEST["action"]) {
		case "action1":

function wsAction1 () {
	global $LoggedAccount, $WS_REQUEST, $AppId;
	$success = FALSE;
	$message = "";
	if (!$LoggedAccount->IsLogged()) {
		$message = "User not authenticated";
	} else {
		// Get the parameters sent to the webservice
		$id = (!isset($WS_REQUEST["id"]) ? 0 : intval($WS_REQUEST["id"]));
		$prop1 = (!isset($WS_REQUEST["p1"]) ? 0 : intval($WS_REQUEST["p1"]));
		$prop2= (!isset($WS_REQUEST["p2"]) ? "" : $WS_REQUEST["p2"]);
		$obj = new MyNewClass1($id);
		$obj->IdApp = $AppId->Id;
		$obj->MyProperty1 = $prop1;
		$obj->MyProperty2 = $prop2;
		$success = $obj->Save();
	// Send a JSON ({success:TRUE|FALSE, message: "text"}) encrypted for security
	Utils::EchoJson( Utils::JsonEncodeSuccessMessage($success, $message), FALSE, TRUE );

To use the core classes of Combu you can take a look at the standard web services in the root of your Combu folder. You can also create your own database tables and manage in the easy OOP way like the core classes by creating a class that extends DataClass, take a look at the class News (in /vendor/skaredcreations/combu/combu/News.php) as basic example.

To contact the web service you can use CombuManager.CallWebservice:

var form = CombuManager.instance.CreateForm();
form.AddField("action", "action1");
form.AddField("id", 0);
form.AddField("p1", 1234);
form.AddField("p2", "my-value");
CombuManager.instance.CallWebservice(CombuManager.instance.GetUrl("addons/my-add-on/my-webservice.php"), form, (string text, string error) => {
	if (string.IsNullOrEmpty(error))
		Hashtable result = text.hashtableFromJson();
		if (result != null)
			bool success = false;
			if (result.ContainsKey("success"))
				bool.TryParse(result["success"].ToString(), out success);
			if (!success && result.ContainsKey("message"))
				error = result["message"].ToString();
			Debug.Log (success + " --- " + error)