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 from clients.

Following the guidelines, 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

This file contains the definition of our add-on, so copy the following code:

<?php

include_once __DIR__ . '/Feedback.php';

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

// Add a menu to /combu/admin
$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 please take also a 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 use in the code with use XYZ at top of your file before using it (for example: use Combu\Account; if you want to instantiate or use the Account class), e.g. take a look at the standard web services in the root folder of Combu.

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 AUTO_INCREMENT,
                    `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;
use Combu\Account;

class Feedback extends DataClass {
    
    const TABLE_NAME = "Feedbacks";
    
    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 on 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 (unpaged)
     * @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";
        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 User object
        $user = new Account($this->IdAccount);
        $array["User"] = $user->ToArray();
        
        return $array;
    }
}

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.

Conclusions

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).

Continues to Part 2.

Leave a Reply

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