Creating a small WebShop extension for Sitemagic CMS

In this chapter we will create a real extension with a GUI (Graphical User Interface) that allows us to enter and save data in our database (in Sitemagic CMS this is called a DataSource), as well as display it. This will be a simple product list that can easily be turned in to a small WebShop for those interested.
Sitemagic CMS features a rich GUI library with most common controls. These take full advantage of Sitemagic CMS being stateful, meaning controls keep their state and value even when a post back is performed (page is submitted), much like a real computer application preserves its state
Please read Building extensions and My first extension before proceeding, for some general information on building extensions for Sitemagic CMS.
Follow the instruction on this page, or download the example from the box in the upper right cornor. Install it to the extensions folder on the server and make sure you enable it from Settings.
To manually build this extension, create a new extension folder named WebShop. Create the necessary files (Main.class.php and metadata.xml) as described in My first extension. Enable the extension from Settings.
Now add the code below. Go through the code and read the comments to understand how it all works.
Main.class.php
<?php

class WebShop extends SMExtension
{
    // Add links to navigation menu during the PreTemplateUpdate stage of the life cycle
    public function PreTemplateUpdate()
    {
        // Only add links to menu if it is installed and enabled
        if (SMExtensionManager::ExtensionEnabled("SMMenu") === true)
        {
            // Get menus (to which our links will be added)
            $menu = SMMenuManager::GetInstance();
            $adminMenu = $menu->GetChild("SMMenuAdmin");

            $extensionName = $this->context->GetExtensionName();

            // Create and add link to admin menu
            if ($adminMenu !== null) // $adminMenu is null if website owner is not logged in
            {
                $linkId = SMRandom::CreateGuid();
                $linkTitle = "Manage products";
                $linkUrl = SMExtensionManager::GetExtensionUrl($extensionName);
                $linkUrl .= "&WebShopFunction=ManageProducts";
                $adminMenu->AddChild(new SMMenuItem($linkId, $linkTitle, $linkUrl));
            }

            // Create and add link to normal navigation menu
            $linkId = SMRandom::CreateGuid();
            $linkTitle = "Display products";
            $linkUrl = SMExtensionManager::GetExtensionUrl($extensionName);
            $linkUrl .= "&WebShopFunction=DisplayProducts";
            $menu->AddChild(new SMMenuItem($linkId, $linkTitle, $linkUrl));
        }
    }

    public function Render()
    {
        $output = ""; // Will hold the content we wish to display

        $idPrefix = $this->context->GetExtensionName();
        $function = SMEnvironment::GetQueryValue("WebShopFunction");
        $loggedIn = SMAuthentication::Authorized();

        $ds = new SMDataSource("WebShopProducts"); // Access products from database

        // Render GUI for managing products if user clicked the ManageProducts link
        // created during the PreTemplateUpdate stage. Only display the GUI if the
        // user is logged in to Sitemagic CMS.
        if ($function === "ManageProducts" && $loggedIn === true)
        {
            // Create GUI for managing products

            // First create GUI controls.

            // This is a drop down list containing existing products
            $listProducts = new SMOptionList($idPrefix . "Products");
            $listProducts->SetAutoPostBack(true);
            $listProducts->SetAttribute(SMInputAttribute::$Style, "width: 200px");

            // This is a text box which allows us to define a product title
            $txtTitle = new SMInput($idPrefix . "Name", SMInputType::$Text);
            $txtTitle->SetAttribute(SMInputAttribute::$Style, "width: 200px");

            // This is a text box which allows us to define a product description
            $txtDescription = new SMInput($idPrefix . "Description", SMInputType::$Textarea);
            $txtDescription->SetAttribute(SMInputAttribute::$Style, "width: 200px");

            // This is a text box which allows us to define the price of our product
            $txtPrice = new SMInput($idPrefix . "Price", SMInputType::$Text);
            $txtPrice->SetAttribute(SMInputAttribute::$Style, "width: 50px");

            // This is a link button which allows us to save our product
            $cmdSave = new SMLinkButton($idPrefix . "Save");
            $cmdSave->SetTitle("Save");

            // This is a link button which allows us to delete a product
            $cmdDelete = new SMLinkButton($idPrefix . "Delete");
            $cmdDelete->SetTitle("Delete");

            // This is a link button which allows us to clear the GUI
            $cmdClear = new SMLinkButton($idPrefix . "Clear");
            $cmdClear->SetTitle("Clear");

            // Load products into product list.
            // See loadProducts(..) function for explanation.
            $this->loadProductList($ds, $listProducts);

            // Load product information into text fields if selected from product list

            // Load product details into text fields if a selection was made from the menu
            if ($listProducts->PerformedPostBack() === true
                && $listProducts->GetSelectedValue() !== "")
            {
                // Select all data ("*") from the Products DataSource where the id attribute
                // is identical to the ID of the product selected from the product list.
                // Since a selection may result in multiple records, an array is returned.
                // In this case we know that only one record will be returned since the id
                // attribute is unique.
                $prods = $ds->Select("*", "id = '" . $listProducts->GetSelectedValue() . "'");

                // Load product details into text fields
                $txtTitle->SetValue($prods[0]["title"]);
                $txtDescription->SetValue($prods[0]["description"]);
                $txtPrice->SetValue($prods[0]["price"]);
            }

            // Save changes if Save button was clicked

            if ($cmdSave->PerformedPostBack() === true)
            {
                if ($listProducts->GetSelectedValue() === "")
                {
                    // No product was selected from the product list.
                    // Therefore we create a new product entry.

                    $newProduct = new SMKeyValueCollection();
                    $newProduct["id"] = SMRandom::CreateGuid();
                    $newProduct["title"] = $txtTitle->GetValue();
                    $newProduct["description"] = $txtDescription->GetValue();
                    $newProduct["price"] = $txtPrice->GetValue();

                    $ds->Insert($newProduct);
                }
                else
                {
                    // Product already exists (selected from product list).
                    // Update existing product with new information.

                    $updates = new SMKeyValueCollection();
                    $updates["title"] = $txtTitle->GetValue();
                    $updates["description"] = $txtDescription->GetValue();
                    $updates["price"] = $txtPrice->GetValue();

                    // Notice the WHERE statement. We only apply the updates to
                    // products with the specified ID from the product list.
                    // In this case we know that there will be only one product
                    // matching our WHERE statement since the id attribute is unique.
                    $ds->Update($updates, "id = '" . $listProducts->GetSelectedValue() . "'");
                }

                // Reload product list to make sure the new or update product
                // is displayed, and clear the GUI to allow for new data to be entered.
                $this->loadProductList($ds, $listProducts);
                $this->clearFields($listProducts, $txtTitle, $txtDescription, $txtPrice);
            }

            // Delete product if Delete button was clicked

            if ($cmdDelete->PerformedPostBack() === true)
            {
                // Delete product selected in product list.
                // It is safe to assume that a product has been selected
                // since Delete button is only rendered when a product is selected.
                // Notice the WHERE statement. We only delete products with the
                // specified ID from the product list.
                // In this case we know that there will be only one product
                // matching our WHERE statement since the id attribute is unique.
                $ds->Delete("id = '" . $listProducts->GetSelectedValue() . "'");

                // Reload product list to make sure the product is no longer
                // displayed, and clear the GUI to allow for new data to be entered.
                $this->loadProductList($ds, $listProducts);
                $this->clearFields($listProducts, $txtTitle, $txtDescription, $txtPrice);
            }

            // Clear all fields if Clear button was clicked.
            // See clearFields(..) function for explanation.

            if ($cmdClear->PerformedPostBack() === true)
                $this->clearFields($listProducts, $txtTitle, $txtDescription, $txtPrice);

            // Render GUI controls.

            // This variable determines whether to display the Delete button or not.
            // It will only be displayed if a product is selected from the product list.
            $displayDelete = ($listProducts->GetSelectedValue() !== "");

            // Render our GUI controls within a table to set it up as
            // a form with labels and input fields. Simply call the Render() function
            // on a GUI control to have it produce its HTML code.
            $output .= "<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\">";
            $output .= " <tr>";
            $output .= "  <td>Products</td>";
            $output .= "  <td>" . $listProducts->Render() . "</td>";
            $output .= " </tr>";
            $output .= " <tr>";
            $output .= "  <td>Title</td>";
            $output .= "  <td>" . $txtTitle->Render() . "</td>";
            $output .= " </tr>";
            $output .= " <tr>";
            $output .= "  <td>Description</td>";
            $output .= "  <td>" . $txtDescription->Render() . "</td>";
            $output .= " </tr>";
            $output .= " <tr>";
            $output .= "  <td>Price</td>";
            $output .= "  <td>" . $txtPrice->Render() . "</td>";
            $output .= " </tr>";
            $output .= " <tr>";
            $output .= "  <td>&nbsp;</td>";
            $output .= "  <td style=\"text-align: right\"><br>";
            $output .= $cmdSave->Render();
            $output .= " | " . $cmdClear->Render();
            $output .= (($displayDelete === true) ? " | " . $cmdDelete->Render() : "");
            $output .= "  </td>";
            $output .= " </tr>";
            $output .= "</table>";
        }
        else if ($function === "DisplayProducts")
        {
            // Display products.
            // The DisplayProducts link was clicked, so instead of rendering
            // the GUI used to manage products, we display existing information
            // in a simple product list.

            // Select all product from DataSource
            $products = $ds->Select("*");

            // Render table and headers
            $output .= "<table cellspacing=\"0\" cellpadding=\"5\" border=\"1\">";
            $output .= " <tr>";
            $output .= "  <td><b>Title</b></td>";
            $output .= "  <td><b>Description</b></td>";
            $output .= "  <td><b>Price</b></td>";
            $output .= " </tr>";

            // Loop through products and render each of them within the table
            foreach ($products as $prod)
            {
                $output .= " <tr>";
                $output .= "  <td>" . $prod["title"] . "</td>";
                $output .= "  <td>" . $prod["description"] . "</td>";
                $output .= "  <td>" . $prod["price"] . "</td>";
                $output .= " </tr>";
            }

            // Close product table
            $output .= "</table>";
        }

        // Return result to Sitemagic CMS to have it displayed in the content area.
        return $output;
    }

    // This function will load products into the product list.
    private function loadProductList($ds, $listProducts)
    {
        // First we clear the existing items by assigning an empty array,
        // and then add an empty item in the beginning.
        $listProducts->SetOptions(array());
        $listProducts->AddOption(new SMOptionListItem("WebShopProductEmpty", "", ""));

        $itemId = null;
        $itemTitle = null;
        $itemValue = null;

        // Loop through all products
        $products = $ds->Select("*");
        foreach ($products as $prod)
        {
            // Create variables used to create item in product list.
            // An item is creating used a unique item ID, a title,
            // and a value. The latter is the unique product ID which
            // can be retrived from the product list by invoking
            // $listProducts->GetSelectedValue().
            $itemId = "WebShopProduct" . $prod["id"];
            $itemTitle = $prod["title"];
            $itemValue = $prod["id"];

            // Create and add list item to product list.
            $listProducts->AddOption(new SMOptionListItem($itemId, $itemTitle, $itemValue));
        }
    }

    // This function is responsible for clearing the GUI controls.
    private function clearFields($listProducts, $txtTitle, $txtDescription, $txtPrice)
    {
        // Clear selection in products list and set an empty value in all input fields.
        $listProducts->SetSelectedValue("");
        $txtTitle->SetValue("");
        $txtDescription->SetValue("");
        $txtPrice->SetValue("");
    }
}

?>
Example can be further improved by adding:
We will leave it up to you to further extend this example, or create your own new extension for Sitemagic CMS.