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

This is the second part of the tutorial Create your first custom add-on, in particular we are creating an add-on to store the feedback messages from our users.

In this part we will create the endpoints of our webservice to communicate with the clients in order to provide them with a method to retrieve the current feedbacks and another one to send a new feedback.

client.php

This file will contains the endpoints of our webservice, in particular it accepts a parameter action that will identify the different endpoints and execute the code for the current endpoint (the endpoints must return a JSON string representing the result to communicate to the client):

  • list: this endpoint returns the paginated list of feedbacks
  • load: this endpoint returns a single feedback loaded by its Id
  • save: this endpoint edits or inserts a feedback
  • delete: this endpoint deletes a feedback

To return the JSON string for the results of our endpoints we can use the traditional json_encode function of PHP or the Combu’s utilities Utils::EchoJson, Utils::JsonEncodeSuccessMessage and Utils::JsonEncodeRowsMessage (see the code below for example).

Let’s translate this concept into code:

<?php

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

use Combu\Feedback;
use Combu\Utils;
use Combu\ErrorMessage;

if (isset($WS_REQUEST["action"])) {
    switch ($WS_REQUEST["action"]) {
        
        case "list":
            wsList();
            break;
        
        case "load":
            wsLoad();
            break;
        
        case "save":
            wsSave();
            break;
        
        case "delete":
            wsDelete();
            break;
        
    }
}
$Database->CloseConnection();
exit();

/**
 * Retrieve a list of feedbacks
 * @global \Combu\AppId $AppId
 * @global array[string] $WS_REQUEST
 */
function wsList() {
    global $AppId, $WS_REQUEST;
    $idAccount = (isset($WS_REQUEST["IdAccount"]) ? intval($WS_REQUEST["IdAccount"]) : 0);
    $limit = (isset($WS_REQUEST["Limit"]) && intval($WS_REQUEST["Limit"]) > 0 ? intval($WS_REQUEST["Limit"]) : DEFAULT_LIST_LIMIT);
    $page = (isset($WS_REQUEST["Page"]) && intval($WS_REQUEST["Page"]) > 0 ? intval($WS_REQUEST["Page"]) : 1);
    $count = 0;
    $results = array();
    $records = Feedback::Load($AppId->Id, $idAccount, $limit, Utils::GetPageOffset($page, $limit), $count);
    foreach ($records as $record) {
        $results[] = $record->ToArray();
    }
    Utils::EchoJson(Utils::JsonEncodeRowsMessage($results, $count, Utils::GetPagesCount($count, $limit)));
}

/**
 * Retrieve a feedback
 * @global \Combu\AppId $AppId
 * @global array[string] $WS_REQUEST
 * @global Combu\Account $LoggedAccount
 */
function wsLoad() {
    global $AppId, $WS_REQUEST, $LoggedAccount;
    $success = FALSE;
    $message = "";
    $id = (isset($WS_REQUEST["Id"]) ? intval($WS_REQUEST["Id"]) : 0);
    $record = new Feedback($id);
    if ($record->Id != $id) {
        $message = "Feedback not found";
    } else {
        $success = TRUE;
        $message = $record->ToJson();
    }
    Utils::EchoJson(Utils::JsonEncodeSuccessMessage($success, $message));
}

/**
 * Create or edit a feedback
 * @global \Combu\AppId $AppId
 * @global array[string] $WS_REQUEST
 * @global Combu\Account $LoggedAccount
 */
function wsSave() {
    global $AppId, $WS_REQUEST, $LoggedAccount;
    $success = FALSE;
    $message = "";
    if (!$LoggedAccount->IsLogged()) {
        $message = ErrorMessage::Get(ERROR_USER_NOT_AUTHENTICATED);
    } else {
        $id = (isset($WS_REQUEST["Id"]) ? intval($WS_REQUEST["Id"]) : 0);
        $record = new Feedback($id);
        if ($record->Id != $id || ($record->Id > 0 && $record->IdAccount != $LoggedAccount->Id)) {
            $message = "Feedback not found";
        } else {
            Utils::FillObjectFromRow($record, $WS_REQUEST);
            if ($record->Id < 1) {
                $record->IdAccount = $LoggedAccount->Id;
            }
            if ($record->Save()) {
                $success = TRUE;
                $message = $record->ToJson();
            } else {
                $message = ErrorMessage::Get(ERROR_UNEXPECTED_GENERIC);
            }
        }
    }
    Utils::EchoJson(Utils::JsonEncodeSuccessMessage($success, $message));
}

/**
 * Delete a feedback
 * @global \Combu\AppId $AppId
 * @global array[string] $WS_REQUEST
 * @global Combu\Account $LoggedAccount
 */
function wsDelete() {
    global $AppId, $WS_REQUEST, $LoggedAccount;
    $success = FALSE;
    $message = "";
    if (!$LoggedAccount->IsLogged()) {
        $message = ErrorMessage::Get(ERROR_USER_NOT_AUTHENTICATED);
    } else {
        $id = (isset($WS_REQUEST["Id"]) ? intval($WS_REQUEST["Id"]) : 0);
        $record = new Feedback($id);
        if ($record->Id != $id || $record->IdAccount != $LoggedAccount->Id) {
            $message = "Feedback not found";
        } else if ($record->Delete()) {
            $success = TRUE;
        } else {
            $message = ErrorMessage::Get(ERROR_UNEXPECTED_GENERIC);
        }
    }
    Utils::EchoJson(Utils::JsonEncodeSuccessMessage($success, $message));
}

Since we coded the constructor of Feedback class to accept a parameter that can either an associative array (used internally when retrieving a row from database) or a number considered as Id, we can easily load a record from database instantiating a new instance of Feedback and passing its Id as parameter to the constructor, as we see in the code above used in the function wsLoad and wsSave.

To retrieve the list of feedbacks we are using the method Load of Feedback class (used in the method wsList in the code above), as we coded in part 1 of this tutorial it accepts the following parameters:

  • App Id: the global variable $AppId is the current app running the webservice
  • Account Id: if you want to filter the feedbacks sent from an account
  • Limit: if you want to get a paginated set of results pass the number of results per page, to get all results pass 0 or NULL
  • Page Offset: if you want to get a paginated set of results pass the utility function Utils::GetPageOffset with the current page number and Limit (see above), to get all results pass 0 or NULL
  • Count: this parameter will be set to the full unpaginated count of records found in the search (useful to returns the number of pages and results count to the client for paginated results)

Endpoint list

This endpoint is used to retrieve the list of feedbacks and accepts the following parameters:

  • IdAccount: To filter the feedbacks sent from a specified account
  • Page: To paginate the list of feedbacks returned (recommended to decrease the overload), pass the number of current page to retrieve or 1 to get all results
  • Limit: To paginate pass the number of results to retrieve or 0 to get all results

The response will have the following properties:

  • total: The total numbers of records found
  • results: The JSON representation list of records retrieved from the current page and filters

Endpoint load

This endpoint is used to retrieve a single feedback by loading from database through its Id, it accepts the following parameters:

  • Id: The Id of the feedback

The response will have the following properties:

  • success: The success of the operation (TRUE if succeeded)
  • message: The JSON representation of the feedback instance if success is TRUE, else the error message

Endpoint save

This endpoint is used either to edit an existing feedback or insert a new one, it accepts the following parameters:

  • Id: The Id of the feedback to edit or 0 to insert a new one
  • Title: The title text
  • Message: The message text
  • Rating: The rating number

The response will have the following properties:

  • success: The success of the operation (TRUE if succeeded)
  • message: The JSON representation of the feedback instance if success is TRUE, else the error message

Endpoint delete

This endpoint is used to delete an existing feedback, it accepts the following parameters:

  • Id: The Id of the feedback to delete

The response will have the following properties:

  • success: The success of the operation (TRUE if succeeded)
  • message: The error message if success is FALSE, else is empty

admin/messages.php

This page will be displayed in the menu of Combu administration website, because we linked it in the call to AddAdminMenu on our addon instance in the previous part (in our addon.php).

We will handle here the list and delete features of feedbacks sent from our users by using the methods that we created inside the Feedback class.

<?php

use Combu\Utils;
use Combu\AddonModule;
use Combu\AppId;
use Combu\Account;
use Combu\Feedback;

$addon = AddonModule::GetAddon("Feedbacks");
$addonMenuIndex = 0;

$error = NULL;
$id = getRequestInput("Id");
$saved = (intval(getRequestInput("saved")) == 1);
$deleted = (intval(getRequestInput("deleted")) == 1);

$feedback = new Feedback($id);

if ($feedback->Id > 0) {
    $action = getRequestInput("action");
    if ($action == "delete") {
        if ($feedback->Id > 0) {
            if ($feedback->Delete()) {
                Utils::RedirectTo($addon->GetMenuLink($addonMenuIndex) . "?deleted=1");
            } else {
                $error = "Could not delete the feedback";
            }
        } else {
            $error = "Invalid feedback";
        }
    }
    $author = new Account($feedback->IdAccount);
} else {
    $feedback = NULL;
    $idApp = getRequestInput("IdApp", 0);
    $username = getRequestInput("Username");
    $account = new Account($username);
    $page = max(array(1, intval(getRequestInput("Page"))));
    $count = 0;
    $feedbacks = Feedback::Load($idApp, $account->Id, DEFAULT_LIST_LIMIT, Utils::GetPageOffset($page, DEFAULT_LIST_LIMIT), $count);
    $pagesCount = Utils::GetPagesCount($count, DEFAULT_LIST_LIMIT);
    $apps = AppId::Load();
}

?>

<?= printAlertDisappear(!$saved ? "" : "The record has been updated.") ?>
<?= printAlertDisappear(!$deleted ? "" : "The record has been deleted.") ?>
<?= printAlertError($error) ?>

<fieldset>
    
    <?php if ($feedback) { ?>
    
        <legend>Feedback message</legend>
        <form id="form-edit" action="get">
            <input type="hidden" name="action" value="delete"/>
            <div class="form-group">
                <label>Date Created</label>
                <input type="text" class="form-control" value="<?= strftime("%d %b %Y %H:%M", Utils::GetTimestamp($feedback->DateCreated)) ?>" disabled="disabled"/>
            </div>
            <div class="form-group">
                <label>Author</label>
                <input type="text" class="form-control" value="<?= htmlentities($author->Username) ?>" disabled="disabled"/>
            </div>
            <div class="form-group">
                <label>Subject</label>
                <input type="text" class="form-control" value="<?= htmlentities($feedback->Title) ?>" disabled="disabled"/>
            </div>
            <div class="form-group">
                <label>Content</label>
                <input type="text" class="form-control" value="<?= htmlentities($feedback->Message) ?>" disabled="disabled"/>
            </div>
            <div class="form-group">
                <input type="submit" class="btn btn-danger" value="Delete"/> or <a href="<?= $addon->GetMenuLink($addonMenuIndex) ?>">Cancel</a>
            </div>
        </form>
    
    <?php } else { ?>
        
        <legend>Feedbacks list</legend>
        
        <form id="form-search" method="post">
            <div class="form-group">
                <label>App Scope</label>
                <select class="form-control" name="IdApp">
                    <option value="0" <?php if ($idApp <= 0) echo ' selected'; ?>>[Any App]</option>
                <?php foreach ($apps as $app) { ?>
                    <option value="<?= $app->Id ?>" <?php if ($idApp == $app->Id) echo ' selected'; ?>><?= htmlentities($app->Name) ?></option>
                <?php } ?>
                </select>
            </div>
            <div class="field">
                <button type="submit" class="btn btn-primary"><i class="fa fa-search"></i> Search</button>
                <button type="button" class="btn btn-default" onclick="document.location.href = '<?= $addon->GetMenuLink($addonMenuIndex) ?>';"><i class="fa fa-undo"></i> Reset</button>
            </div>
        </form>
        
        <table class="table-records" width="100%">
            <thead>
                <tr>
                    <th width="1"></th>
                    <th width="150">App</th>
                    <th width="150">Date</th>
                    <th width="150">Author</th>
                    <th>Subject</th>
                </tr>
            </thead>
            <tbody>
                <?php if ($count == 0) { ?>
                    
                    <tr>
                        <td colspan="6">No results</td>
                    </tr>
                        
                <?php } else { ?>
                        
                    <?php foreach ($feedbacks as $feedback) { ?>
                        <?php
                        $app = new AppId($feedback->IdApp);
                        $author = new Account($feedback->IdAccount);
                        ?>
                        <tr>
                            <td>
                                <button class="btn btn-primary" onclick="document.location.href='<?= $addon->GetMenuLink($addonMenuIndex) ?>&Id=<?= $feedback->Id ?>';" title="Read"><i class="fa fa-edit"></i></button>
                            </td>
                            <td><?= htmlentities($app->Id > 0 ? $app->Name : "<em>Not available</em>") ?></td>
                            <td><?= htmlentities(strftime("%d %b %Y %H:%M", Utils::GetTimestamp($feedback->DateCreated))) ?></td>
                            <td><?= htmlentities($author->Id > 0 ? $author->Username : "<em>Not available</em>") ?></td>
                            <td><?= htmlentities($feedback->Title) ?></td>
                        </tr>
                    <?php } ?>

                <?php } ?>
            </tbody>
            <tfoot>
                <?php if ($count > 0) { ?>
                    <tr>
                        <td colspan="4">
                            <div class="navpages">
                                <form method="post">
                                    <input type="hidden" name="IdApp" value="<?= $idApp ?>"/>
                                    <h6>
                                        <?= $count ?> result(s) in <?= $pagesCount ?> page(s)
                                    </h6>
                                    <div class="form-row">
                                        <div class="col-auto">
                                            <select name="Page" class="form-control">
                                            <?php for ($i = 1; $i <= $pagesCount; $i++) { ?>
                                                <option value="<?= $i ?>" <?php if ($i == $page) echo ' selected'; ?>>Page <?= $i ?></option>
                                            <?php } ?>
                                            </select>
                                        </div>
                                        <div class="col-auto">
                                            <input type="submit" class="btn btn-default" value="Go"/>
                                        </div>
                                    </div>
                                </form>
                            </div>
                        </td>
                    </tr>
                <?php } ?>
            </tfoot>
        </table>
    
    <?php } ?>
    
</fieldset>

Conclusions

In this part we created the webservice for the client with all key endpoints to manage the feedbacks from Unity.

In the next final part of this tutorial we will create the Unity scripts to communicate with the server and list, show, insert and delete feedbacks.

Continues to Part 3.

3 comments on “Create your first custom add-on: Feedbacks – Part 2/3

Leave a Reply

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