Dolphin 7 Tutorial: My First Module

1. Getting Started.

2. Simplest module.

3. Adding admin panel.

4. Blog functionality.

5. What to do next.

1. Getting Started.

In this tutorial, we will create a simple Dolphin module. In the beginning, this module will show a text page only. Then, we will add an admin section for the module. At the end we will create some basic functionality, like a blog system.

This tutorial covers things like a simple module structure, creating user pages and admin pages, creating config values for your module, forms and adding admin menu items.

The main purpose of this tutorial is to help you get started with the new Dolphin 7 modules structure and basic functionality.

To proceed with this tutorial you need:

  • already installed Dolphin 7, you can get the latest Dolphin version here and detailed installation instructions are here.
  • basic PHP knowlege, object oriented programming knowledge is more than welcome. You can learn about PHP here.
  • basic MySQL queries knowledge, you can learn about MySQL server and MySQL queries syntax here.

The things you will not get in this tutorial:

  • will not teach you PHP, MySQL, Javascript, HTML, CSS
  • will not teach you how to edit and upload files to the server
  • will not teach you about every Dolphin feature. There are many Dolphin features which are more complex, and mentioning them here may detract you from the main purpose of this tutorial. For a complete list of Dolphin programming features and references to the documentation in the code, check here.
  • will not tech you how to create new templates. A templates creation guide is here.
  • will not teach you how to translate Dolphin to other languages. A translation manual is here.
  • will not teach security aspects of writing the code. You are responsible if you are hacked through your own module.

Learning Dolphin gives you many advantages:

  • you will be able to make modifications to your site
  • if you are writing a modification, especially as a Dolphin module, it will allow you to transfer your modification more easily between Dolphin version upgrades.
  • you can make money by selling your modifications in Unity Market or offering your service for others in the same Unity Market.
  • it will help you to get certified - because there are strict requirements if you want to certify your mod.

Let's start with the simplest thing in the beginning.

2. Simplest module.

There is a prepared zip package with the simplest Dolphin 7 module here. Download it, unpack and upload to the modules/me/ folder. Most probably there is no me folder in the modules directory, so just create it by yourself. Me is a vendor name. For the real module, you need to rename it to the real vendor name, but for now me is fine for everybody. It will be the bloggie folder in the modules/me/ directory. Bloggie is the module name. It is good practice to name the folder where module's files are located with the name of the module, it is just more clear. In the end it will be some type of blog module with the name Bloggie.

Lets describe each file in the module. This is almost the minimal set of files for the module:

bloggie/classes
directory with all module classes

bloggie/classes/MeBlggConfig.php
Module config class. In most cases you don't need to modify it - it inherits system class which has all the necessary functionality to get configuration values.

bloggie/classes/MeBlggDb.php
Module database class. It is strongly recommended that you write all SQL queries in this class only, then you can call ready functions from the code.

bloggie/classes/MeBlggModule.php
Module controller class - where the main work happens.

bloggie/classes/MeBlggTemplate.php
Module template class. You don't need to change this class. All the necessary functionality is inherited from the base class.

bloggie/install
Module installation directory, where all installation files are located, like SQL files and language files.

bloggie/install/config.php
Module installation config file. We will take a closer look at this later.

bloggie/install/info
Installation information messages folder. If you need to display information messages upon module install/uninstall, you will need to place files here - also special instructions are needed to point to in the install config file to display these messages properly.

bloggie/install/installer.php
Module installer class. You can add some custom installation scripts and override default behavior.

bloggie/install/langs
Module languages must be located in this dir. The language file name must be a php file with the name of two letters of the language code.

bloggie/install/langs/en.php
Default English language file.

bloggie/install/sql
SQL script for module install and uninstall are here.

bloggie/install/sql/install.sql
Module installation SQL file.

bloggie/install/sql/uninstall.sql
Module un-installation SQL file.

bloggie/request.php
File which routes all requests to the MeBlggModule class. You don't need to change it in most cases.

bloggie/templates
Module templates directory. You need to place *.html, *.css and image files here, according to the structure below.

bloggie/templates/base
Base template folder. You don't need to create a separate folder for each template (but you can do that). Just place all files into the base folder, and regardless of the current chosen template, all template files will be taken from here. Also, all HTML template files are located in this folder.

bloggie/templates/base/css
CSS files must be located here.

bloggie/templates/base/images
Images files must be located here.

bloggie/templates/base/images/icons
Image icons must be located here.

bloggie/templates/base/main.html
Our custom template file.

The location of each file is important, because there are some functions to plug-in to each file automatically. If the file is not located properly, it will not be possible to plug it in easily.

The foregoing just gives you the basic view of the module's structure, and now you know where to find files to modify templates, or where all module's SQL queries are located.

Now you need to install the module via the admin panel. Go to Dolphin admin panel -> Tools -> Modules. Scroll down to the Not Installed Modules block. It will show the Bloggie version 1.0.0 by Me module here if you uploaded all the files properly. Select it and click install. The module will be installed and you will see the Installation of: Bloggie Done message in the top block of this page. To test the module open the following page:

http://www.your-site.com/your-path/m/bloggie/home

or if Apache mod rewrite is not enabled, or you don't have the .htaccess file in the Dolphin root folder for some strange reason, you can try to open the following url:

http://www.your-site.com/your-path/modules/?r=bloggie/home

The only thing this module does is to display the following message: "For a community to be whole and healthy, it must be based on people's love and concern for each other". Not so much, but it is an example of a working result, shows that you don't have any bugs and your site cannot be hacked through this page.

Now we will make some easy modifications to this page. But, before proceeding to any modifications, let's disable all template caches in the Dolphin admin panel -> Settings -> Advanced Settings -> Template. It will make life easier by saving some time for you - you don't need to clear the cache manually after each change.

2.1. Change Text

This phrase on the page is located in the language file, so all we need is to change the translation. The easiest way is to go to the Dolphin admin panel -> Settings -> Language settings and change the phrase here, but in this case the phrase will not be remembered by our module and this modification would be lost upon module reinstall. To change it in the module forever, we need to edit the bloggie/install/langs/en.php file.

File content looks like this (I omitted php tags and copyright info):

$sLangCategory = 'My Bloggie';

$aLangContent = array(
    '_me_blgg' => 'My Bloggie',
    '_me_blgg_text' => 'For a community to be whole and healthy, it must be based on people\'s love and concern for each other.',
);

The language file is regular php file with one array, where array indexes are language keys and values are translations. Now we need to change the translation only. Take care when editing translations, some characters must be escaped properly as it is a regular php file and regular php string. Also, if you are translating the language file to another language, you need to be sure that you are using UTF-8 encoding during editing of this file. Change it in the following way:

$sLangCategory = 'My Bloggie';

$aLangContent = array(
    '_me_blgg' => 'My Bloggie',
    '_me_blgg_text' => 'Hello World!',
);

If you are brave enough you may enter you own text.

After this change, you will not see the new message on the desired page until you reinstall the module, or you can select the module in the list of installed modules in the Dolphin admin panel -> Tools -> Modules and click Recompile language(s). Now the text is changed on your page, of course, after a page refresh.

But what is this good for ? A simple text page can be created using an easier method ... you maybe already know that you can create such pages in the Dolphin page builder. Ok, lets add some dynamics to make a difference from static pages!

2.2. Adding dynamic content.

For some users, it maybe useful to see the difference between server time and user time, so this change will show both server and user time.

It is easier to begin with the things we already know. First we need to add new language keys:

$sLangCategory = 'My Bloggie';

$aLangContent = array(
    '_me_blgg' => 'My Bloggie',
    '_me_blgg_server_time' => 'Server time:',
    '_me_blgg_user_time' => 'User time:',
);

I assume that you didn't forget to recompile language files for the module after this change!

Then, we will modify the template file bloggie/templates/base/main.html from the old one:

<div style="text-align:center;">
    <bx_text:_me_blgg_text />
</div>

to a new one:

<div style="text-align:center;">
    <bx_text:_me_blgg_server_time /> __server_time__
    <br />
    <bx_text:_me_blgg_user_time />
    <script type="text/javascript">
        document.write(new Date());
    </script>
</div>

Some things need clarifying:

  • bx_text is a special instruction. It outputs a translation for the key using the currently selected language.
  • server_time is a template variable which is not defined yet.
  • For the user time, we use javascript, because javascript is executed in a browser, on the user side, so the time is user time here.

Now, try to refresh the page to see what was changed, If everything is done right, you will see something like this:

Server time: __server_time__
User time: Fri Jan 22 2010 13:46:07 GMT+1100

It almost works perfectly. The only thing is the server time display. All the underscores are displayed, because we have not defined a server variable yet. To define this we need to change the following code in the bloggie/classes/MeBlggModule.php file:

    function actionHome () {
        $this->_oTemplate->pageStart();
        $aVars = array ();
        echo $this->_oTemplate->parseHtmlByName('main', $aVars);
        $this->_oTemplate->pageCode(_t('_me_blgg'), true);
    }

to this one:

    function actionHome () {
        $this->_oTemplate->pageStart();
        $aVars = array (
            'server_time' => date('r'),
        );
        echo $this->_oTemplate->parseHtmlByName('main', $aVars);
        $this->_oTemplate->pageCode(_t('_me_blgg'), true);
    }

Before describing all these lines, refresh the page to see the result - Voila!

Server time: Fri, 22 Jan 2010 13:56:54 +1100
User time: Fri Jan 22 2010 13:56:55 GMT+1100

The time is almost the same because I'm on a local server and probably you are,too. But in the case of a remote server in another timezone you will see a difference.

  • _oTemplate is an automatically defined template class, In our case it is the instance of the MeBlggTemplate? class
  • pageStart tells you that output will be below
  • pageCode tells you that output is completed, and the page will be displayed
    • the first parameter is page name, and we take it from the language file using _t function
    • the second parameter tells the script to wrap the output in a box
  • parseHtmlByName function outputs the main.html file by making all the substitutions. It accepts the array $aVars of server variables - in this case we declared the server_time template variable, which is used in the template as server_time.

Some other things which are good to know at this stage:

Take note of the function name - actionHome. Every function which begins with the action word is accessible from the browser, so be careful and make sure that you don't begin another function with the action keyword. Make other note too: the URL of the page is http://www.your-site.com/your-path/m/bloggie/home. The Home keyword in the function name is directly dependent on the URL. The trick is that the URL is transformed to the function name. For example - http://www.your-site.com/your-path/m/bloggie/test_page will be transformed to the actionTestPage function. Also, you maybe already noticed that http://www.your-site.com/your-path/m/bloggie/ URL is working too. This is because actionHome is special function which is called when no one is pointed to the URL. Also, these action functions can accept parameters, but this will be described later.

The simplest Dolphin module is completed. Now you can use this to start developing you first Dolphin module, or use it as a framework for your own different modules. Almost every extension can be started from such a structure. Make sure that you take a look at the bloggie/install/config.php file. This file must be heavily changed for your own module, and this file has comments on each line to help you in changing it.

3. Adding admin panel.

We want to improve our mod and add a format for the date and allow disabling user time. We definitely need some admin settings for this purpose.

Our admin panel will have the following parts:

  • admin page creation
  • adding settings to module install and uninstall
  • print setting form on newly created admin pages
  • a link in the admin modules section to the module setting page

3.1. Admin page creation.

Add the following code to the bloggie/classes/MeBlggModule.php file as a class method:

    function actionAdministration () {

        if (!$GLOBALS['logged']['admin']) {
            $this->_oTemplate->displayAccessDenied ();
            return;
        }

        $this->_oTemplate->pageStart();

        echo DesignBoxAdmin (_t('_me_blgg'), 'It works!', '', '', 11);
        
        $this->_oTemplate->pageCodeAdmin (_t('_me_blgg'));
    }

The pageCodeAdmin function is similar to the pageCode function, but it displays the admin page instead of the user page. Also, this function accepts only one parameter - page title.

Also the difference in the code is that we check if admin only can access this page. The $GLOBALS['logged']['admin'] variable is set to true if admin is logged in. Additionally, you can check if regular members are logged in by using the $GLOBALS['logged']['member'] variable. If none of these values are true, then a guest is accessing your page. The displayAccessDenied function displays a page with an "access denied" message - in this case, we don't need to care much about that, and we can just return from the function or even exit. The $GLOBALS['logged'] does not always exist, and is initialized in the check_logged function. In our case this function is called in the bloggie/request.php file.

Another new function here is DesignBoxAdmin. It wraps our content in a nice box. The box title is the first parameter and box content is the second one. There is an optional 3rd and 4th parameters where you can define the box menu and bottom bar. 5th parameter is design box type, 1 - no padding, 11 - box with padding. Also, there is an analog of this function in the user side called DesignBoxContent. You can examine existing Dolphin code for a lot of examples.

To test the page, you need to login to the admin panel and open the http://www.your-site.com/your-path/m/bloggie/administration URL.

There is no artificial code, and hopefully you have made everything properly and the admin page is displayed with the "It works!" phrase. If there is a problem, then review all the steps once again and try to find where there is a mistake.

3.2. Adding module settings.

At this stage we need to modify the install.sql and uninstall.sql files for the first time. We need to change the installation config file and indicate that these files must be processed. To do this we need to change the following section in the bloggie/install/config.php file:

    'install' => array(
        'update_languages' => 1,
    ),
    'uninstall' => array (
        'update_languages' => 1,
    ),

to the following:

    'install' => array(
        'update_languages' => 1,
        'execute_sql' => 1,
    ),
    'uninstall' => array (
        'update_languages' => 1,
        'execute_sql' => 1,
    ),

It will execute install.sql and uninstall.sql files automatically upon modules install/uninstall.

Then, place the following code into install.sql:

SET @iMaxOrder = (SELECT `menu_order` + 1 FROM `sys_options_cats` ORDER BY `menu_order` DESC LIMIT 1);
INSERT INTO `sys_options_cats` (`name`, `menu_order`) VALUES ('My Bloggie', @iMaxOrder);
SET @iCategId = (SELECT LAST_INSERT_ID());
INSERT INTO `sys_options` (`Name`, `VALUE`, `kateg`, `desc`, `Type`, `check`, `err_text`, `order_in_kateg`, `AvailableValues`) VALUES
('me_blgg_date_format', 'Y-m-d H:i', @iCategId, 'Format for server date/time', 'digit', '', '', '1', ''),
('me_blgg_enable_js_date', 'on', @iCategId, 'Show user time', 'checkbox', '', '', '2', '');

and the following into uninstall.sql:

-- settings
SET @iCategId = (SELECT `ID` FROM `sys_options_cats` WHERE `name` = 'My Bloggie' LIMIT 1);
DELETE FROM `sys_options` WHERE `kateg` = @iCategId;
DELETE FROM `sys_options_cats` WHERE `ID` = @iCategId;

The most important thing is that all setting options are in the sys_options database table. It is better to assign every setting option some category which is stored in the sys_options_cats database table, because it makes it easier to delete created categories in uninstall and display them. Also, it is good practice to make a prefix for all of your setting names to avoid conflicts with other mods.

Now, we need to use newly created settings, so we change the actionHome function in the following way:

    function actionHome () {
        $this->_oTemplate->pageStart();

        $sDateFormat = getParam ('me_blgg_date_format');
        $isShowUserTime = getParam('me_blgg_enable_js_date') ? true : false;
        $aVars = array (
            'server_time' => date($sDateFormat),
            'bx_if:show_user_time' => array(
                'condition' => $isShowUserTime,
                'content' => array(),
            ),
        );
        echo $this->_oTemplate->parseHtmlByName('main', $aVars);
        $this->_oTemplate->pageCode(_t('_me_blgg'), true);
    }

and we change the template as shown below:

<div style="text-align:center;">
    <bx_text:_me_blgg_server_time /> __server_time__
    <bx_if:show_user_time>
        <br />
        <bx_text:_me_blgg_user_time />
        <script type="text/javascript">
            document.write(new Date());
        </script>
    </bx_if:show_user_time>
</div>

Lets describe a few new things:

  • the getParam function is used to get a setting option with a specified name. Please note that in the case of a checkbox, it will return on as the true value or an empty string as a false value.
  • bx_if template construction allows adding conditional processing to the template. The condition is in the condition field, and any variables which you need to use inside bx_if clauses in the template should be placed in the content subarray. In our case, there is no such variable and this array is empty.

Almost everything is done, and it is time to test it now. Reinstall the module, since we have changed the installation config and installation files, and refresh the http://www.your-site.com/your-path/m/bloggie/home page. If everything is done right, the result should be as follows:

Server time: 2010-01-22 17:45
User time: Fri Jan 22 2010 17:45:41 GMT+1100

Now, everything works except the fact that we cannot change any settings options we just created. So, we now proceed to creating a setting options form.

3.3. Changing the settings form.

Change the actionAdministration function the following way:

    function actionAdministration () {

        if (!$GLOBALS['logged']['admin']) { // check access to the page
            $this->_oTemplate->displayAccessDenied ();
            return;
        }

        $this->_oTemplate->pageStart(); // all the code below will be wrapped by the admin design

        $iId = $this->_oDb->getSettingsCategory(); // get our setting category id
        if(empty($iId)) { // if category is not found display page not found
            echo MsgBox(_t('_sys_request_page_not_found_cpt'));
            $this->_oTemplate->pageCodeAdmin (_t('_me_blgg'));
            return;
        }

        bx_import('BxDolAdminSettings'); // import class

        $mixedResult = '';
        if(isset($_POST['save']) && isset($_POST['cat'])) { // save settings
            $oSettings = new BxDolAdminSettings($iId);
            $mixedResult = $oSettings->saveChanges($_POST);
        }

        $oSettings = new BxDolAdminSettings($iId); // get display form code
        $sResult = $oSettings->getForm();
                   
        if($mixedResult !== true && !empty($mixedResult)) // attach any resulted messages at the form beginning
            $sResult = $mixedResult . $sResult;

        echo DesignBoxAdmin (_t('_me_blgg'), $sResult, '', '', 11); // dsiplay box
        
        $this->_oTemplate->pageCodeAdmin (_t('_me_blgg')); // output is completed, admin page will be displaed here
    }   

then add the following class method to the modules/me/bloggie/classes/MeBlggDb.php file:

    function getSettingsCategory() {
        return $this->getOne("SELECT `ID` FROM `sys_options_cats` WHERE `name` = 'My Bloggie' LIMIT 1");
    }

Clarification:

  • the getSettingsCategory method is placed into MeBlggDb since all database queries must be in this file.
  • the getOne method returns the value of ID field in the SELECT SQL query. There are other handy functions to get the data from database. Refer to inc/classes/BxDolDb.php for complete list.
  • _oDb is automatically defined in the MeBlggDb class
  • the MsgBox function wraps text in a nice message box
  • the bx_import function automatically imports a class by its name, and it can load classes with BxDol, BxBase, BxTempl prefixes if they are correctly named and located. Also, it can load module classes if the module config is passed as the second parameter.
  • the BxDolAdminSettings class is used to display and save settings. Just use it as shown above. Make a note that the saveSettings function returns a result message which we append at the beginning of the result string.

3.4. Adding a link to a newly created page to the admin modules sub-menu.

Replace the content of install.sql with the following code:

-- settings
SET @iMaxOrder = (SELECT `menu_order` + 1 FROM `sys_options_cats` ORDER BY `menu_order` DESC LIMIT 1);
INSERT INTO `sys_options_cats` (`name`, `menu_order`) VALUES ('My Bloggie', @iMaxOrder);
SET @iCategId = (SELECT LAST_INSERT_ID());
INSERT INTO `sys_options` (`Name`, `VALUE`, `kateg`, `desc`, `Type`, `check`, `err_text`, `order_in_kateg`, `AvailableValues`) VALUES
('me_blgg_permalinks', 'on', 26, 'Enable friendly permalinks in Bloggie', 'checkbox', '', '', '0', ''),
('me_blgg_date_format', 'Y-m-d H:i', @iCategId, 'Format for server date/time', 'digit', '', '', '1', ''),
('me_blgg_enable_js_date', 'on', @iCategId, 'Show user time', 'checkbox', '', '', '2', '');

-- permalinks
INSERT INTO `sys_permalinks` VALUES (NULL, 'modules/?r=bloggie/', 'm/bloggie/', 'me_blgg_permalinks');

-- admin menu
SET @iMax = (SELECT MAX(`order`) FROM `sys_menu_admin` WHERE `parent_id` = '2');
INSERT IGNORE INTO `sys_menu_admin` (`parent_id`, `name`, `title`, `url`, `description`, `icon`, `order`) VALUES
(2, 'me_blgg', '_me_blgg', '{siteUrl}modules/?r=bloggie/administration/', 'My Bloggie by Me', 'file', @iMax+1);

and uninstall.sql with the following:

-- settings
SET @iCategId = (SELECT `ID` FROM `sys_options_cats` WHERE `name` = 'My Bloggie' LIMIT 1);
DELETE FROM `sys_options` WHERE `kateg` = @iCategId;
DELETE FROM `sys_options_cats` WHERE `ID` = @iCategId;
DELETE FROM `sys_options` WHERE `Name` = 'me_blgg_permalinks';

-- permalinks
DELETE FROM `sys_permalinks` WHERE `standard` = 'modules/?r=bloggie/';

-- admin menu
DELETE FROM `sys_menu_admin` WHERE `name` = 'me_blgg';

Also, we need to change this section in the modules/me/bloggie/install/config.php file, since we have modified the sys_permalinks database table:

    'install' => array(
        'update_languages' => 1,
        'execute_sql' => 1,
        'recompile_permalinks' => 1,
    ),
    'uninstall' => array (
        'update_languages' => 1,
        'execute_sql' => 1,
        'recompile_permalinks' => 1,
    ),

the recompile_permalinks instruction was added above.

We need to specify permalinks, because they are needed for correct link representation in case of an enabled mod_rewrite module in Apache and when this module is disabled. To correctly enable the permalinks mechanism we need to specify several things:

  • add a setting option which enables or disables permalinks in the module. In our case we have added the me_blgg_permalinks setting option. Please note that we have added this option to a special category (category id is equal to "26").
  • add the record to the sys_permalinks database table where we specify the newly created setting option and two URLs:
    • modules/?r=bloggie/ - when permalinks are disabled
    • m/bloggie/ - when permalinks are enabled
  • add recompile_permalinks in the modules/me/bloggie/install/config.php file. It tells the installer to recompile cache with permalinks when we install or uninstall the module.

The link to admin Modules submenu is added at the end. We have specified:

  • "2" indicates that it will be added to the Modules admin menu
  • me_blgg is an internal name. It is similar to a normal database prefix
  • _me_blgg is the language key which is used to display the module title
  • URL is specifiled without permalinks enabled, and it is transformed to mod_rewrite URL automatically, if permalinks are enabled. Also, we used siteUrl in the URL, but it will be replaced with the full site URL with the slash at the end.
  • My Bloggie by Me is the module description.
  • file is a menu icon - FontAwesome? icon name without prefix. It can be usual image icon in the format - "modules/me/bloggie/|icon.png", then icon image file must be placed into the modules/me/bloggie/templates/base/images/icons/ folder and named icon.png. The size should be 16x16 pixels.
  • @iMax+1 is order. Order is calculated in the previous string, and in this case, it will be added to the end of the list - which is preferable behavior.

Full module re-installation is needed because we have modified the config.php and install.sql files.

The module is completed and you can download resulted module here to compare with your own, or to find mistakes if something went wrong.

4. Blog functionality.

I have prepared new package to not waste the time for things already explained. Please uninstall your current Bloggie module and delete it from the modules/me directory, and then download a new one from here and install it. I have just removed unnecessary things and added settings and language keys related to Blog functionality.

Our Bloggie will consist of several pages:

  • the latest blog posts page (our module homepage)
  • Blog post view
  • Adding a blog post page
  • Editing the Blog post page

The main thing in this section is how to deal with forms, and includes several helpful hints.\

4.1. Creating the database structure.

We will have only one database table with blog posts:

CREATE TABLE `me_blgg_posts` (
    `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
    `title` VARCHAR( 255 ) NOT NULL ,
    `text` TEXT NOT NULL ,
    `author_id` INT UNSIGNED NOT NULL ,
    `added` INT UNSIGNED NOT NULL ,
    INDEX ( `author_id` )
);

add the above lines to the beginning of the install.sql file. Also, we need to add a "drop" database to the beginning of the uninstall.sql file:

DROP TABLE `me_blgg_posts`;

Since you are aware of the basic SQL synatax, I will not go into a description of each symbol. In the general, our Bloggie will have title and text, and other hidden fields are necessary too:

  • id - a unique identifier of each blog post. It is also the table primary key
  • author_id - author's profile id
  • added - blog post creation date/time

I have indexed the author_id field because we may have a page with all user's posts in the future.

4.2. Creating the add post page.

Add the following method to the MeBlggModule class:

    function actionAdd () {

        if (!$GLOBALS['logged']['member'] && !$GLOBALS['logged']['admin']) { // check access to the page
            $this->_oTemplate->displayAccessDenied ();
            return;
        }

        $this->_oTemplate->pageStart(); // all the code below will be wrapped by the user design

        bx_import ('BxTemplFormView'); // import forms class

        $oForm = new BxTemplFormView ($this->aForm); // create foprms class

        $oForm->initChecker(); // init form checker

        if ($oForm->isSubmittedAndValid ()) { // if form is submitted and not form errors were found, save form data

            $aValsAdd = array ( // add additional fields
                'added' => time(),
                'author_id' => $_COOKIE['memberID'],
            );                        
            $iEntryId = $oForm->insert ($aValsAdd); // insert data to database

            if ($iEntryId) { // if post was successfully added
                $sRedirectUrl = BX_DOL_URL_ROOT . $this->_oConfig->getBaseUri() . 'view/' . $iEntryId;
                header ('Location:' . $sRedirectUrl); // redirect to created post view page
                exit;
            } else {
                MsgBox(_t('_Error Occured')); // if error occured display erroro message
            }                

        } else {

            echo $oForm->getCode (); // display form, if the form is not submiyyed or data is invalid

        }

        $this->_oTemplate->pageCode(_t('_me_blgg_page_title_add'), true); // output is completed, display all output above data wrapped by user design
    }

then modify the MeBlggModule class constructor the following way:

    function MeBlggModule(&$aModule) {        
        parent::BxDolModule($aModule);

        $this->aForm = array(

            'form_attrs' => array(
                'name'     => 'form_bloggie',
                'action'   => '',
                'method'   => 'post',
            ),      

            'params' => array (
                'db' => array(
                    'table' => 'me_blgg_posts',
                    'key' => 'id',
                    'submit_name' => 'submit_form',
                ),
            ),
                  
            'inputs' => array(

                'title' => array(
                    'type' => 'text',
                    'name' => 'title',
                    'caption' => _t('_me_blgg_form_caption_title'),
                    'required' => true,
                    'checker' => array (
                        'func' => 'length',
                        'params' => array(3,255),
                        'error' => _t ('_me_blgg_form_err_title'),
                    ),
                    'db' => array (
                        'pass' => 'Xss',
                    ),
                ),            
                
                'text' => array(
                    'type' => 'textarea',
                    'name' => 'text',
                    'caption' => _t('_me_blgg_form_caption_text'),
                    'required' => true,
                    'html' => 2,
                    'checker' => array (
                        'func' => 'length',
                        'params' => array(20,64000),
                        'error' => _t ('_me_blgg_form_err_text'),
                    ),                    
                    'db' => array (
                        'pass' => 'XssHtml',
                    ),                    
                ),

                'submit' => array (
                    'type' => 'submit',
                    'name' => 'submit_form',
                    'value' => _t('_Submit'),
                    'colspan' => true,
                ),            

            ),            
        );
    }

The array we have placed into the constructor is our form description. The reason we have placed it in the description instead of the function is that we will reuse this array in the future when we create our edit post page.

The form description array can be described the following way:

form_attrs are regular HTML attributes which will be added to the <form> tag.

params are special function parameters. This can have several parameters, but most important is the db sub-array which describes the table to save form data to.

  • table is the database table name to save data to
  • key is the database primary key field (it is used to update data)
  • submit_name is the name of the submit html form element and is used to determine automatically when the form is submitted.

the inputs array describes every form element, and each element consists of the following fields:

  • type is input type, like text, textarea, etc
  • name is input the type name it also must match database field name
  • caption is the indicated form element name
  • required displays a red asterisk near the caption. Please note that it displays a red asterisk only. No real checking for mandatory fields is made if this field is true. For real checking see below.
  • checker describes the checker function for the field. This is a place where real checking takes place.
    • func is the checking function, like length, preg, avail, etc.
    • params are checking function params. Params varies depending on the exact function.
    • error is the message to display near the field if checking failed.
  • db describes the database field to save input data to. The most important is the following parameter:
    • pass is the function to pass data through, it validates data properly - to not allow an inappropriate value in a particular field. The value of this field may be Xss, XssHtml, Int, Float, Date, etc.

This doesn't describe all the possible values of this array. For a complete list please refer to the inc/classes/BxDolForm.php file.

Let's come back to the actionAdd function. First, we check that only members and admins can add blog posts. We have already described the $GLOBALS['logged'] array. Then pageStart tells us that output will be below. Then, we import the BxTemplFormView class and create a class instance by passing the form description array to the class constructor. initChecker initializes the checker function. It is needed if the form was submitted with wrong values. Then, if the form was not submitted or it has invalid data the the form will be displayed (getCode function is called). Otherwise we will save the submitted data by calling the insert form function. Before saving, we added additional database fields, and after successful saving we are redirected to the post view page (which we will create in the next step). This page displays when we call the pageCode function.

You can already try the form functionality, and see in the database if the data was saved. Let's proceed to the blog view page, to be able to view submitted data in a user friendly manner.

4.3. View Bloggie post page.

Add the following function to the MeBlggModule class:

    function actionView ($iEntryId) {

        $aEntry = $this->_oDb->getEntryById ((int)$iEntryId);
        if (!$aEntry) { // check if entry exists
            $this->_oTemplate->displayPageNotFound ();
            return;
        }

        $this->_oTemplate->pageStart(); // all the code below will be wrapped by the user design

        $aVars = array (
            'title' => $aEntry['title'],
            'text' => $aEntry['text'],
            'author' => getNickName($aEntry['author_id']),
            'added' => defineTimeInterval($aEntry['added']),
        );
        echo $this->_oTemplate->parseHtmlByName('view', $aVars); // display post

        $this->_oTemplate->pageCode($aEntry['title'], true); // output is completed, display all output above data wrapped by user design
    }

This is the first time where our function accepts any parameters:

  • $iEntryId function parameter will be passed when we call the following URL: http://www.your-site.com/your-path/m/bloggie/view/15, in this case $iEntryId will be equal to "15".
  • getNickName is the system function which returns the user name by user id.
  • defineTimeInterval function generates a human readable representation of the unix timestamp date/time.
  • displayPageNotFound displays a message like "Page Not Found" along with HTTP 404 headers.
  • getEntryById is the database function which is created below.

Add the following method to the MeBlggDb class:

    function getEntryById ($iEntryId) {
        return $this->getRow("SELECT * FROM `" . $this->_sPrefix . "posts` WHERE `id` = '$iEntryId'");
    }

We have already described database functions, and there is nothing to clarify. The only thing is that it is good practice to use the $this->_sPrefix variable as the database prefix for the module's tables.

The last thing is the bloggie/templates/base/view.html file:

<div>
    <h1>__title__</h1>
    <p>__text__</p>
    <p style="text-align:right; font-style:italic;"><bx_text:_me_blgg_posted_by /> __author__ (__added__)</p>
</div>

This file looks extremely simple.

Try to add some Bloggie post and you will be redirected to the view post page.

Now the only page we are missing is the page with the posts list.

4.4. Creating Bloggie homepage with a posts list.

Change the actionHome function the following way:

    function actionHome () {
        $this->_oTemplate->pageStart(); // all the code below will be wrapped by the user design

        $aPosts = $this->_oDb->getAllPosts (getParam('me_blgg_max_posts_to_show')); // get all posts from database
        foreach ($aPosts as $sKey => $aRow) { // add human readable values to the resulted array
            $aPosts[$sKey]['url'] = BX_DOL_URL_ROOT . $this->_oConfig->getBaseUri() . 'view/' . $aRow['id'];
            $aPosts[$sKey]['added'] = defineTimeInterval($aRow['added']);
            $aPosts[$sKey]['author'] = getNickName($aRow['author_id']);
        }        
        $aVars = array ( // define template variables
            'bx_repeat:posts' => $aPosts,
        );
        echo $this->_oTemplate->parseHtmlByName('main', $aVars); // output posts list

        $this->_oTemplate->pageCode(_t('_me_blgg'), true); // output is completed, display all output above data wrapped by user design
    }

then place the following code into the bloggie/templates/base/main.html file:

    <bx_repeat:posts>
        <h2>
            <a href="__url__">__title__</a>
        </h2>
        <p>__text__</p>
        <p style="text-align:right; font-style:italic;"><bx_text:_me_blgg_posted_by /> __author__ (__added__)</p>
        <hr />
    </bx_repeat:posts>

And we have added a new database class method:

    function getAllPosts ($iLimit) {
        return $this->getAll("SELECT *, LEFT(`text`, 256) AS `text` FROM `" . $this->_sPrefix . "posts` ORDER BY `added` DESC LIMIT $iLimit");
    }

We have used the new template construction bx_repeat:posts here. This allows us to iterate through an array of similar data, Blog posts are the data to iterate through, in this case. Also, we used a new config class method - getBaseUri It returns modules/?r=bloggie/ or m/bloggie/ depending on the permalinks settings. It is mandatory to generate all links this way, so the links will work when mod_rewrite is disabled.

Almost everything is done here. The last thing we will add to Bloggie module will be edit post page.

4.5. Edit the post page.

Add the following method to the MeBlggModule class:

    function actionEdit ($iEntryId) {

        $aEntry = $this->_oDb->getEntryById ((int)$iEntryId);
        if (!$aEntry) { // check if entry exists
            $this->_oTemplate->displayPageNotFound ();
            return;
        }

        if ((!$GLOBALS['logged']['member'] && !$GLOBALS['logged']['admin']) || $aEntry['author_id'] != $_COOKIE['memberID']) { // check access to the page
            $this->_oTemplate->displayAccessDenied ();
            return;
        }

        $this->_oTemplate->pageStart(); // all the code below will be wrapped by the user design

        bx_import ('BxTemplFormView'); // import forms class

        $oForm = new BxTemplFormView ($this->aForm); // create forms class

        $oForm->initChecker($aEntry); // init form checker

        if ($oForm->isSubmittedAndValid ()) { // if form is submitted and there is no form errors were found, save form data

            $iRes = $oForm->update ($iEntryId); // update data in database

            if ($iRes) { // if post was successfully added
                $sRedirectUrl = BX_DOL_URL_ROOT . $this->_oConfig->getBaseUri() . 'view/' . $iEntryId;
                header ('Location:' . $sRedirectUrl); // redirect to updated post view page
                exit;
            } else {
                MsgBox(_t('_Error Occured')); // if error occured display error message
            }                

        } else {

            echo $oForm->getCode (); // display form, if the form is not submitted or data is invalid

        }

        $this->_oTemplate->pageCode(_t('_me_blgg_page_title_edit'), true); // output is completed, display all output above data wrapped by user design
    }

This function is pretty similar to the actionAdd method - with the following differences:

  • there is additional access checking to allow only the post owner to edit it
  • initChecker receives $aEntry as a parameter to fill our form with values, since it is an edit form
  • instead of save we called the update form function with the post's primary id number, to indicate the post to update

The last thing to do is to update the view post page and add an edit link to it. The edit post link will be added near the post title. To achieve this, we need to modify the actionView method in the following way:

    function actionView ($iEntryId) {

        $aEntry = $this->_oDb->getEntryById ((int)$iEntryId);

        if (!$aEntry) { // check if entry exists
            $this->_oTemplate->displayPageNotFound ();
            return;
        }

        $this->_oTemplate->pageStart(); // all the code below will be wrapped by the user design

        $aVars = array (
            'title' => $aEntry['title'],
            'text' => $aEntry['text'],
            'author' => getNickName($aEntry['author_id']),
            'added' => defineTimeInterval($aEntry['added']),
            'bx_if:edit' => array(
                'condition' => ($GLOBALS['logged']['member'] || $GLOBALS['logged']['admin']) && $aEntry['author_id'] == $_COOKIE['memberID'],
                'content' => array(
                    'edit_url' => BX_DOL_URL_ROOT . $this->_oConfig->getBaseUri() . 'edit/' . $aEntry['id'],
                ),
            ),        
        );
        echo $this->_oTemplate->parseHtmlByName('view', $aVars); // display post

        $this->_oTemplate->pageCode($aEntry['title'], true); // output is completed, display all output above data wrapped by user design
    }

and place the code below into the bloggie/templates/base/view.html file:

<div>
    <h1>
        __title__
        <bx_if:edit>
            <a href="__edit_url__"><bx_text:_me_blgg_edit /></a>
        </bx_if:edit>
    </h1>
    <p>__text__</p>
    <p style="text-align:right; font-style:italic;"><bx_text:_me_blgg_posted_by /> __author__ (__added__)</p>
</div>

We have passed a new variable to the template with the edit post link, but is has a condition that only the owner can edit the post.

Try this new functionality by editing already created posts.

You can download the resulting module to compare with your own, here.

Congratulations! The first Dolphin 7 module is now completed. I hope that you are full of ideas on how to extend this module for your own needs or create a new one from scratch.

5. What to do next.

There are many resources where you can get some Dolphin knowledge and share your experience:

  • BoonEx Unity Forums - report bugs, ask for the help and help others if you see that you have an answer.
  • BoonEx Blog - find helpful blog posts of others and make your post if you think you have something to share about your Dolphin experience.
  • BoonEx Market - download free and paid modules for Dolphin or post your own mods.
  • BoonEx Tickets System - check the current Dolphin development stage or get latest source code.
  • BoonEx Wiki - Dolphin documentation.
  • BoonEx Agents - get support from your personal BoonEx agent

You probably have many questions or are willing to share your new module or experience with others after reading this post, so use the links above to do it in the right place.

Last modified 9 years ago Last modified on Oct 16, 2015, 12:54:57 AM

Attachments (4)

Download all attachments as: .zip

 
Below is the legacy version of the Boonex site, maintained for Dolphin.Pro 7.x support.
The new Dolphin solution is powered by UNA Community Management System.
Fork me on GitHub