Create your first custom add-on: Feedbacks – Part 1/3

In this post we will create a custom add-on to collect the feedbacks of our users and display them in the administration website.

An add-on consists of a folder placed inside /combu/addons which contains some files:

  • addon.php: this is the main file and contains the definition of the current add-on object (the script is executed during the initialization process of a Combu request and provide an object $addon of AddonModule type that you will set for your add-on, read later); it’s loaded by the system both in the calls to web services and in the pages of the administration website; if this file is missing then the add-on is not loaded
  • setup_database.xml (optional): this file contains eventually the SQL queries to create the tables on Combu database in a structured XML format (for example if you need to create a new table, checkout below for an example)
  • PHP classes (optional): files for your own classes
  • PHP admin scripts (optional): files to be loaded from menu in the administration website
  • PHP client scripts and any other file you may need on server in your add-on

…and of course some Unity scripts if you need to communicate with the add-on needs from clients.

For the purpose of this tutorial, create a folder called feedbacks inside /combu/addons on your server.

In this folder you will create the following files:

  • addon.php: the add-on definition
  • setup_database.xml: the file containing the SQL to create our table on database
  • Feedback.php: the class that performs the operations on the database and is used in both the webservice and the administration page
  • client.php: the handler for our web service to communicate from Unity
  • admin/messages.php: the page to read the messages in the administration website from a link in the main menu

 

addon.php

Following the guidelines and create a new folder called feedbacks in the directory /combu/addons on the web server.

In this folder create a new file called addon.php with the following code:

<?php
// Include our classes
include_once './Feedback.php';

// Give a name to this add-on
$addon->Name = "Feedbacks";
$addon->Version = "1.0";

// Add a menu to /admin website in a group box 'Feedbacks'
// - first parameter is the display text of the item in the menu list of the group box
// - second parameter is a file path to load as content relative to the add-on folder
$addon->AddAdminMenu("Messages", "admin/messages.php");

This file is automatically included during the initialization process of Combu server API and presumes the variable $addon to be created as a new AddonModule instance right before including your addon.php file.

You can find all core classes of Combu server in the folder /vendor/skaredcreations/combu/combu/ and look at the official documentation of Combu PHP server framework online at https://www.skaredcreations.com/api/combu/v3-server/

Remember that in any PHP file you need to declare the classes that you uses in the code with use XYZ at top of your file (for example “use Combu\Account”) before using it, for example take a look at the standard web services in the root folder.

 

setup_database.xml

(Optional) Create a new file called setup_database.xml in your add-on’s directory with the following content:

<?xml version="1.0" encoding="utf-8"?>
<pma_xml_export version="1.0" xmlns:pma="https://www.phpmyadmin.net/some_doc_url/">
    <pma:structure_schemas>
        <pma:database name="combu" collation="utf8_general_ci" charset="utf8">
            <pma:table name="Feedbacks">
                CREATE TABLE `Feedbacks` (
                 `Id` BIGINT(20) NOT NULL,
                 `IdApp` BIGINT(20) NOT NULL,
                 `IdAccount` BIGINT(20) NOT NULL,
                 `DateCreated` DATETIME NOT NULL,
                 `DateUpdated` DATETIME NOT NULL,
                 `Title` VARCHAR(255) NOT NULL,
                 `Message` TEXT NULL,
                 `Rating` INT NOT NULL,
                 PRIMARY KEY (`Id`)
                ) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8;
            </pma:table>
        </pma:database>
    </pma:structure_schemas>
</pma_xml_export>

It is an XML file that contains the SQL queries to be executed when the users of this add-on (you or your add-on’s customers) use the page /combu/_setup/database in order to create on a new server the tables of this add-on.

 

Feedback.php

Create a file called Feedback.php in the add-on’s directory with the following content as beginning, it’s basically the declaration of our class Feedback inside the namespace Combu that extends Combu/DataClass to provide access to the database by using its own public properties that should match the letter-case of fields in the table:

<?php

namespace Combu;

use Combu\Utils;

class Feedback extends DataClass {

    // This is the table name in the database
    const TABLE_NAME = "Feedbacks";

    // The public properties should match the fields in the table
    // so properties will be auto-set from associative array of records in _loadByRow
    // (or passed as JSON from client requests)
    public $Id = 0;
    public $IdApp = 0;
    public $IdAccount = 0;
    public $DateCreated = NULL;
    public $DateUpdated = NULL;
    public $Title = "";
    public $Message = "";
    public $Rating = 0;

    // Contructor
    public function __construct($src = null, $stripSlashes = false) {
        if (!empty($src)) {
            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 from database
     *
     * @param int $idApp Filter by IdApp
     * @param int $idAccount Filter by IdAccount
     * @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)
     * @return Feedback[] Returns the array of records
     */
    public static function Load ($idApp = 0, $idAccount = 0, $limit = NULL, $offset = NULL, &$count = NULL) {
        $where = "";
        $orderBy = "Id DESC"; // most recent first
        if ($idApp > 0) {
            $where .= ($where ? " AND " : "") . sprintf("(IdApp = %d)", $idApp);
        }
        if ($idAccount > 0) {
            $where .= ($where ? " AND " : "") . sprintf("(IdAccount = %d)", $idAccount);
        }
        return self::_load(self::GetTableName(__CLASS__), __CLASS__, $where, $orderBy, $limit, $offset, $count);
    }

    /**
     * Save the record in the database
     *
     * @global Database $Database
     * @return bool Returns TRUE on success
     */
    public function Save() {
        global $Database;
        $this->DateUpdated = Utils::GetCurrentDateTimeFormat();
        if ($this->Id > 0) {
            $query = sprintf("UPDATE %s SET Title = '%s', Message = '%s', Rating = %d, DateUpdated = %s WHERE Id = %d",
                    self::GetTableName(__CLASS__),
                    $Database->Escape($this->Title),
                    $Database->Escape($this->Message),
                    $this->Rating,
                    $Database->EscapeDate($this->DateUpdated),
                    $this->Id);
        } else {
            $this->DateCreated = $this->DateUpdated;
            $query = sprintf("INSERT INTO %s (IdApp, IdAccount, Title, Message, Rating, DateCreated, DateUpdated) VALUES (%d, %d, '%s', '%s', %d, %s, %s)",
                    self::GetTableName(__CLASS__),
                    $this->IdApp,
                    $this->IdAccount,
                    $Database->Escape($this->Title),
                    $Database->Escape($this->Message),
                    $this->Rating,
                    $Database->EscapeDate($this->DateCreated),
                    $Database->EscapeDate($this->DateUpdated));
        }
        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;
    }

    /**
     * Returns this object in associative array format
     * @return string[] The properties of this object as associative array
     */
    public function ToArray() {
        $array = parent::ToArray();
        
        // Add the User's array representation to the output as extra info (for webservices)
        $user = new Account($this->IdAccount);
        $array["User"] = $user->ToArray();
    }
}

The constructor uses protected methods contained inside DataClass that fill the properties from the associative array passed or from a record in the database found by search.

Most of classes that you will create and that require a new table in the database will look like this one, so take your time to familiarize with the small code above (the override of ToArray method is useful in this demo because we’re going to create a list of feedbacks in the client UI  so in the override we pass the serialization of the authors).

 

…To be continued in Part 2/3 (not available yet)…

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.