1
irmtfan
How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/4/28 6:42

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


In this tutorial we only want to show you the most standard way of writing XOOPS modules in a very basic and brief way to create standard tables by fetching data from database with the following goals:

1- Using Xoops Core API as much as possible.

2- Do not use any hard-code method in coding. for example:

2-1- Any HTML codes in PHP files. Or PHP codes in HTML files.

2-2- Any smarty variable that doing more than one job.

2-3- Any file like image, CSS, JavaScript that is called with a direct link in HTML, PHP files and any CSS codes being in HTML files instead of CSS.

2-4- Any self-defined functions in modules that are already defined in the core and doing the same thing. They just conflicts with each other.

2-5- Any direct access to database (eg: by using $GLOBALS["xoopsDB"]) and queries directly reading table information.

2-6- Any part of code that do not read/recognize the XoopsLocal class and local css/js/language files.

3- Find easiest, best, consistent and most advanced solutions for regular xoops coding obstacles with the following priority.

3-1- user side codes.
As a developer/debugger you should always try to find a user side solution. it is much better than manipulating php codes.

3-1-1- css: CSS is the best choice. You should always try to find a solution using css
pros: it is OS/Browser independent. even a very simple webmaster knows some css codes. It is local aware and multilingual.
cons: Nothing

3-1-2- html: It is good in combine with css

3-1-3- smarty: it is the third choice if you can not solve the issue using css/html
cons: it is not local aware. Xoops maybe use another template engine in the future.

3-1-4- javascript: it is the last resort in user side programming.
big disadvantage: user may turn the js off in the browser.

3-2- server side codes.

3-2-1- php: try to hack php codes only if you could not find a solution using css/html/smarty/js

3-2-2- mysql: altering/changing database is the last resort.

4- Make your module compatible with future xoops core even with xoops 2.6 as much as possible. So you have less trouble in the future.

5- build full div tables in html instead of using "table" "th" "tr" "td"

Also in this tutorial we will show you real examples from Publisher as an advance content module and Userlog as a logger module.

Now lets start step by step:

1- Before start writing any module it would be good to be more familiar with xoops modules structure. even if you are not a developer it will help you to understand the functionality of Xoops modules.

I recommend any xoops user to read Standard Module Structure
Guidelines
up to section 2.8.

It will help you to be more familiar with xoops system.

2- before write a module, start with some questions:

2-1- Is there any similar module that will work for me?
If you can find a similar module maybe you can contribute in developing/ bug fixing that module instead of writing a module from scratch. for example there is a lot of content, gallery and download modules in xoops which you can use and develop more or customize for your needs. honestly i see no mean to write any more content module in xoops.

2-2- Is there any similar functionality in other modules which i could borrow.

Maybe it seems odd but it will be good to announce in xoops.org forums about your futur plans for a module and your needs. Please explain what you want to do. Usually there are some modules with similar functionality which you can use in your module.

3- Start by writing xoops_version.php and mysql.sql

It will help you to find your module structure. so it would be clear for you what you want to do in the future.

Keep in mind that Xoops core always read your xoops_version.php before loading your module.

== START OF xoops_version.php ==
3-1- xoops_version.php
see an example of xoops_version.php from publisher.
So we can review them one by one:

3-1-1- $modversion:
It is a reserved name which xoops core find module data from. you should use only this name.

3-1-2- Module information: name,description, ...
- Note: many of the following will be shown in module > about page.
// Name of the module
$modversion['name'] = _MI_PUBLISHER_MD_NAME;

- You should get the name from language file.

// Version of the module
$modversion['version'] = 1.0;


- for versioning please pay attention that xoops core can just accept this format:
VERSION = YYYY.XX
So it will ignore any version after the second number after point.
so these versions are same for xoops core:
1.011
1.014
OR:
12.126
12.129

So if your module needs an upgrade you should change the first or second number after point.

// Brief description of the module
$modversion['description'] = _MI_PUBLISHER_MD_DESC;
// Author
$modversion['author'] = "Xuups.com";
// Credits - other people but not shown in about page
$modversion['credits'] = "w4z004, hsalazar, Mithrandir, fx2024, Ackbarr, Mariuss, Marco, Michiel, phppp, outch, Xvitry, Catzwolf, Shine, McDonald, trabis, Mowaffak, Bandit-x, Shiva";
// the arg of the help url, If your module have a help file
$modversion['help']  = 'page=help';
// License
$modversion['license'] = 'GNU GPL 2.0';
$modversion['license_url'] = "www.gnu.org/licenses/gpl-2.0.html/";
// If it’s an official module = 1 but usually it is not
$modversion['official'] = 0;


// Name of the directory that holds the module.
$modversion['dirname'] = basename(__DIR__);

- use basename(__DIR__) for better cloning.

// Path and name of the module’s logo
$logo_filename $modversion['dirname'] . "_logo.png";

if (
file_exists(XOOPS_ROOT_PATH "/modules/" $modversion['dirname'] . "/images/" $logo_filename)) {
    
$modversion['image'] = "images/{$logo_filename}";
} else {
    
$modversion['image'] = "images/module_logo.png";
}

- While you could simply add your logo like this:
// Path and name of the module’s logo
$modversion['image'] = "images/mymodule_logo.png";

The above from publisher is the best way for cloning.

3-1-3- module install/update/uninstall special scripts:
You can have some special scripts for your module to be execute on module install/update/uninstall.
// Install
$modversion['onInstall'] = 'include/install.php';
// Update
$modversion['onUpdate'] = 'include/update.php';


here you can define your module scripts which will be executed when Xoops core want to install/uninstall/update your module.
It is better to write all needed functions in one file and name it include/module.php to standardize it in all xoops modules.

so instead of what you see in publisher we recommend you to use one file like userlog module
$modversion['onUpdate'] = 'include/module.php';
$modversion['onUninstall'] = 'include/module.php';


for more information look at:
OnInstall/OnUpdate/OnUninstall functions tutorial

3-1-4- moduleadmin class:
You should use moduleadmin class framework in your module otherwise it will not work in xoops.
//about
$modversion['release_date']     = '2011/11/17';
$modversion['module_status']    = "RC";
$modversion['min_php']          = '5.2.0';
$modversion['min_xoops']        = '2.5.0';
$modversion['min_db']           = array('mysql'=>'5.0.7''mysqli'=>'5.0.7');
$modversion['min_admin']        = '1.1';
$modversion['dirmoduleadmin'] = '/Frameworks/moduleclasses/moduleadmin';
$modversion['icons16']        = '../../Frameworks/moduleclasses/icons/16';
$modversion['icons32']        = '../../Frameworks/moduleclasses/icons/32';


The above settings will be used in about/index page, as well as in menu.php file

All xoops modules should use moduleadmin class. so you can define icon paths here.

// Admin things
$modversion['hasAdmin'] = 1;
$modversion['adminindex'] = "admin/index.php";
$modversion['adminmenu'] = "admin/menu.php";
// If you want your module has a sub menu in system menu set it to 1
$modversion['system_menu'] = 1;


- admin/menu.php will be used by moduleadmin class to create your menu in admin area

3-1-5- tables:
If your module has tables you should define theme.
// Sql file (must contain sql generated by phpMyAdmin or phpPgAdmin)
$modversion['sqlfile']['mysql'] = "sql/mysql.sql";

// Tables created by sql file (without prefix!)
$i 0;
$modversion['tables'][$i] = 'mod_' $modversion['dirname'] . '_log';

$i++;
$modversion['tables'][$i] = 'mod_' $modversion['dirname'] . '_set';

$i++;
$modversion['tables'][$i] = 'mod_' $modversion['dirname'] . '_stats';


- as you can see we should follow this standard for module table names: "mod_" . $dirname . "_TABLE"

3-1-6- search functionality:
// Search
$modversion['hasSearch'] = 1;
$modversion['search']['file'] = "include/search.inc.php";
$modversion['search']['func'] = "publisher_search";

- The above is for search functionality:
It is better to write the 'func' name without $dirname to avoid hard-code:
$modversion['search']['func'] = $modversion["dirname"] . "_search";


You can find more information about xoops search system in this post:
https://xoops.org/modules/newbb/viewtopic.php?post_id=349697#forumpost349697

3-1-7- module links in mainmenu block:
// Menu
$modversion['hasMain'] = 1;


- shows your module has functionality in user side (in modules/MODULE/*.*)
It will show a link of your module in xoops 'mainmenu' block which show modules/MODULE/index.php page.

global $xoopsModule;
if (
is_object($xoopsModule) && $xoopsModule->getVar('dirname') == $modversion['dirname']) {
    global 
$xoopsModuleConfig$xoopsUser;
    
$isAdmin false;
    if (
is_object($xoopsUser)) {
        
$isAdmin $xoopsUser->isAdmin($xoopsModule->getVar('mid'));
    }
    
// Add the Submit new item button
    
$allowsubmit = (isset($xoopsModuleConfig['perm_submit']) && $xoopsModuleConfig['perm_submit'] == 1) ? true false;
    
$anonpost = (isset($xoopsModuleConfig['permissions_anon_post']) && $xoopsModuleConfig['permissions_anon_post'] == 1) ? true false;
    if (
$isAdmin || ($allowsubmit && (is_object($xoopsUser) || $anonpost))) {
        
$modversion['sub'][1]['name'] = _MI_PUBLISHER_SUB_SMNAME1;
        
$modversion['sub'][1]['url'] = "submit.php?op=add";
    }

    
// Add the Search button
    
$allowsearch = (isset($xoopsModuleConfig['perm_search']) && $xoopsModuleConfig['perm_search'] == 1) ? true false;
    if (
$allowsearch) {
        
$modversion['sub'][2]['name'] = _MI_PUBLISHER_SUB_SMNAME3;
        
$modversion['sub'][2]['url'] = "search.php";
    }
}
// Add the Archive button
$modversion['sub'][3]['name'] = _MI_PUBLISHER_SUB_ARCHIVE;
$modversion['sub'][3]['url'] = "archive.php";

- show sub links in the 'mainmenu' block.
As you can see you can prevent some users to some sub links like submit.php

3-1-8- module blocks:
If your module has a block you should define them here.
$modversion['blocks'][$i]['file'] = "views.php";
$modversion['blocks'][$i]['name'] = _MI_USERLOG_BLOCK_VIEWS;
$modversion['blocks'][$i]['description'] = _MI_USERLOG_BLOCK_VIEWS_DSC;
$modversion['blocks'][$i]['show_func'] = $modversion['dirname'] . "_views_show";
$modversion['blocks'][$i]['edit_func'] = $modversion['dirname'] . "_views_edit";
$modversion['blocks'][$i]['options'] = "10|0|1|-1|0|count|DESC";
$modversion['blocks'][$i]['template'] = $modversion['dirname'] . "_block_views.html";


- show that this module has block. you can see we can avoid hard-coded $dirname.
- The file for block functions (show_func and edit_func) will be located in modules/MODULE/blocks
eg: modules/MODULE/blocks/views.php
- show_func: is the function that will be parsed when the block is visible.
- edit_func: is the function that just add a form in Blocks Administration (modules/system/admin.php?fct=blocksadmin&op=edit&bid=BLOCK_ID) for 'options' field.
a module block may not have edit_func if it dont need any extra options.
- options: are default options only will be stored in module install. (Note: So in upgrade they will not be changed in database if you change them here!!!)
- template: the template for this block located in modules/MODULE/templates/blocks
eg: modules/MODULE/templates/blocks/userlog_block_views.html

3-1-9- module templates:
Your module should use templates for user and admin side.
// Templates - if you dont define 'type' it will be 'module' | '' -> templates
$i 0;
$modversion['templates'][$i]['file'] = $modversion['dirname'] . '_admin_sets.html';
$modversion['templates'][$i]['type'] = 'admin'// $type = 'blocks' -> templates/blocks , 'admin' -> templates/admin , 'module' | '' -> templates
$modversion['templates'][$i]['description'] = 'list of userlog setting';

- Templates of your module will show a nice and formatted data in user side.
- Try always use one template for one page/section of your module. More templates means less hard-code.
- Note that you should avoid any direct php output like echo, print_r and ...
- Template types are: 'module' (default), 'admin', 'blocks' (if you dont define it will be 'module')
- In the current xoops 2.5.6 'description' is not used anywhere so you dont need to add a language definition.

3-1-10- module configurations:
Xoops core use 3 tables to save core and module configs:
_config
_configcategory
_configoption

So you dont need to have extra tables for your module configurations.

These tables used for storing module and core configs in database.
configs can be changed in admin > Preferences > MODULE:
xoops256/modules/system/admin.php?fct=preferences&op=showmod&mod=MID

and configs are in modules/MODULE/xoops_version.php
example:
$i=0;
$modversion['config'][$i]['name'] = 'status';
$modversion['config'][$i]['title'] = '_MI_USERLOG_STATUS';
$modversion['config'][$i]['description'] = '_MI_USERLOG_STATUS_DSC';
$modversion['config'][$i]['formtype'] = 'select';
$modversion['config'][$i]['valuetype'] = 'int';
$modversion['config'][$i]['default'] = 1;
$modversion['config'][$i]['options'] = array(_MI_USERLOG_ACTIVE => 1_MI_USERLOG_IDLE => 0);

$i++;
$modversion['config'][$i]['name'] = 'postlog';
$modversion['config'][$i]['title'] = '_MI_USERLOG_POSTLOG';
$modversion['config'][$i]['description'] = '_MI_USERLOG_POSTLOG_DSC';
$modversion['config'][$i]['formtype'] = 'yesno';
$modversion['config'][$i]['valuetype'] = 'int';
$modversion['config'][$i]['default'] = 1;
$modversion['config'][$i]['options'] = array();

_config table:
the below information will be stored in config table:
conf_id: auto generate id
conf_modid: module id
conf_catid: 0 not used in 2.5x series but reserve for 2.6
conf_name: $modversion['config'][$i]['name']
conf_title: $modversion['config'][$i]['title']
conf_value: The value selected by user from $modversion['config'][$i]['options']. The default is: $modversion['config'][$i]['default'] which will be stored once the module is installed.
conf_desc: $modversion['config'][$i]['description']
conf_formtype: $modversion['config'][$i]['formtype']
conf_valuetype: $modversion['config'][$i]['valuetype']
conf_order: $i : number of config in the $modversion['config'] array

A side note about changing conf_value:
the conf_value will be changed only by:
a - install a fresh module which $modversion['config'][$i]['default'] will be stored(as i described above).
b - select by user (as i described above)
c - In update when formtype or valuetype was changed in xoops_version.php $modversion['config'][$i]['default'] will be stored.
A developer may decide to change the value by this trick!!!

_configcategory table:
In 2.5.x only used by core and not used for modules. reserve for xoops 2.6

_configoption table:
information comes from $modversion['config'][$i]['options'] will be stored in this table.
if empty($modversion['config'][$i]['options']) => nothing will be stored in this table.
As an example for this option:
$modversion['config'][$i]['options'] = array(_MI_USERLOG_ACTIVE => 1_MI_USERLOG_IDLE => 0);


Below information will be stored:
confop_id : auto generate option id
confop_name: _MI_USERLOG_ACTIVE (will be parsed to user own language)
confop_value: 1
conf_id: the linked id in config table.

Note: options only will be stored in install and update process.

== END OF xoops_version.php ==

3-2- mysql.sql
Also if your module have some kind of data you need to define your database tables.
Currently the standard for table names is this:
mod_MODULE-DIRNAME-IN-LOWERCASE_MODULE-TABLENAME

for example look at userlog mysql.sql

here you can find tables:
mod_userlog_log
mod_userlog_set
mod_userlog_stats

Then you should create a class for each table and in the future any access to database will be done by using those classes.
see in userlog one class is defined for each table:
userlog/class/log.php for mod_userlog_log table
userlog/class/setting.php for mod_userlog_set table
userlog/class/stats.php for mod_userlog_stats table

Now lets start in depth with an example from publisher module.
we want to see how we can write a class for table publisher_categories in sql file.
at first you can see the table fields and structure in sql file:
CREATE TABLE `PUBLISHER_categories` ( 
  `
categoryidint(11NOT NULL auto_increment
  `
parentidint(11NOT NULL default '0'
  `
namevarchar(100NOT NULL default ''
  `
descriptiontext NOT NULL
  `
imagevarchar(255NOT NULL default ''
  `
totalint(11NOT NULL default '0'
  `
weightint(11NOT NULL default '1'
  `
createdint(11NOT NULL default '1033141070'
  `
templatevarchar(255NOT NULL default ''
  `
headertext NOT NULL
  `
meta_keywordstext NOT NULL
  `
meta_descriptiontext NOT NULL
  `
short_urlvarchar(255NOT NULL default ''
  `
moderatorint(6NOT NULL default '0'
  
PRIMARY KEY (`categoryid`), 
  
KEY parentid (parentid
ENGINE=MyISAM;

then see how to define the class.
all classes in modules for working with database tables, extend XoopsObject class located in XOOPSCORE/kernel/object.php. therefore XoopsObject will be the heart of any module for accessing to database and run any query. (read/write/stats)
The class filename in publisher is category.php

you can see how to define your fields and their types:
class PublisherCategory extends XoopsObject 

    
/** 
     * @var PublisherPublisher 
     * @access public 
     */ 
    
public $publisher null

    
/** 
     * @var array 
     */ 
    
public $_categoryPath false

    
/** 
     * constructor 
     */ 
    
public function __construct() 
    { 
        
$this->publisher PublisherPublisher::getInstance(); 
        
$this->initVar("categoryid"XOBJ_DTYPE_INTnullfalse); 
        
$this->initVar("parentid"XOBJ_DTYPE_INTnullfalse); 
        
$this->initVar("name"XOBJ_DTYPE_TXTBOXnulltrue100); 
        
$this->initVar("description"XOBJ_DTYPE_TXTAREAnullfalse255); 
        
$this->initVar("image"XOBJ_DTYPE_TXTBOXnullfalse255); 
        
$this->initVar("total"XOBJ_DTYPE_INT1false); 
        
$this->initVar("weight"XOBJ_DTYPE_INT1false); 
        
$this->initVar("created"XOBJ_DTYPE_INTnullfalse); 
        
$this->initVar("template"XOBJ_DTYPE_TXTBOXnullfalse255); 
        
$this->initVar("header"XOBJ_DTYPE_TXTAREAnullfalse); 
        
$this->initVar("meta_keywords"XOBJ_DTYPE_TXTAREAnullfalse); 
        
$this->initVar("meta_description"XOBJ_DTYPE_TXTAREAnullfalse); 
        
$this->initVar("short_url"XOBJ_DTYPE_TXTBOXnullfalse255); 
        
$this->initVar("moderator"XOBJ_DTYPE_INTnullfalse0); 
        
//not persistent values 
        
$this->initVar("itemcount"XOBJ_DTYPE_INT0false); 
        
$this->initVar('last_itemid'XOBJ_DTYPE_INT); 
        
$this->initVar('last_title_link'XOBJ_DTYPE_TXTBOX); 
        
$this->initVar("dohtml"XOBJ_DTYPE_INT1false); 
    }


Then you should write another class by extending XoopsPersistableObjectHandler for access to table:
/** 
 * Categories handler class. 
 * This class is responsible for providing data access mechanisms to the data source
 * of Category class objects. 
 * 
 * @author  marcan <marcan@notrevie.ca> 
 * @package Publisher 
 */ 
class PublisherCategoryHandler extends XoopsPersistableObjectHandler 

    
/** 
     * @var PublisherPublisher 
     * @access public 
     */ 
    
public $publisher null

    
/** 
     * @param null|object $db 
     */ 
    
public function __construct(&$db
    { 
        
$this->publisher PublisherPublisher::getInstance(); 
        
parent::__construct($db"publisher_categories"'PublisherCategory'"categoryid""name");
    }

publisher_categories is your table name
PublisherCategory is your Object class name.
categoryid is your table increment key field
name is your table second key field.

You need to read XoopsObject class functions completely and carefully. In this tutorial we will use this class a lot.

As you can see we have an instance of publisher helper object to use it anywhere we need that helper.
for example to access another table in this handler. (eg: in joining purposes)
$this->publisher->getHandler('item')->table

means publisher_items table.

4- write a common.php file for your module.

In this file you will write basic needs like loading Core/Module functions/classes and definitions for future using in all of your other functions in module.
see an example in publisher/include/common.php.

defined("XOOPS_ROOT_PATH") or die("XOOPS root path not defined");

define("PUBLISHER_DIRNAME"basename(dirname(__DIR__)));
define("PUBLISHER_URL"XOOPS_URL '/modules/' PUBLISHER_DIRNAME);
define("PUBLISHER_IMAGES_URL"PUBLISHER_URL '/images');
define("PUBLISHER_ADMIN_URL"PUBLISHER_URL '/admin');
define("PUBLISHER_UPLOADS_URL"XOOPS_URL '/uploads/' PUBLISHER_DIRNAME);
define("PUBLISHER_ROOT_PATH"XOOPS_ROOT_PATH '/modules/' PUBLISHER_DIRNAME);
define("PUBLISHER_UPLOADS_PATH"XOOPS_ROOT_PATH '/uploads/' PUBLISHER_DIRNAME);

xoops_loadLanguage('common'PUBLISHER_DIRNAME);

include_once 
PUBLISHER_ROOT_PATH '/include/functions.php';
include_once 
PUBLISHER_ROOT_PATH '/include/constants.php';
include_once 
PUBLISHER_ROOT_PATH '/include/seo_functions.php';
include_once 
PUBLISHER_ROOT_PATH '/class/metagen.php';
include_once 
PUBLISHER_ROOT_PATH '/class/session.php';
include_once 
PUBLISHER_ROOT_PATH '/class/publisher.php';
include_once 
PUBLISHER_ROOT_PATH '/class/request.php';

$debug false;
$publisher PublisherPublisher::getInstance($debug);


see how it defines all needs.

5- write a module class for your module.

It will help you to put general functions in that class for accessing your module information. generally you can name the file the same as your module dirname.
for example in publisher it is publisher/class/publisher.php:
defined("XOOPS_ROOT_PATH") or die("XOOPS root path not defined");

class 
PublisherPublisher
{
    var 
$dirname;
    var 
$module;
    var 
$handler;
    var 
$config;
    var 
$debug;
    var 
$debugArray = array();

    protected function 
__construct($debug)
    {
        
$this->debug $debug;
        
$this->dirname =  basename(dirname(__DIR__));
    }

    static function &
getInstance($debug false)
    {
        static 
$instance false;
        if (!
$instance) {
            
$instance = new self($debug);
        }
        return 
$instance;
    }

    function &
getModule()
    {
        if (
$this->module == null) {
            
$this->initModule();
        }
        return 
$this->module;
    }

    function 
getConfig($name null)
    {
        if (
$this->config == null) {
            
$this->initConfig();
        }
        if (!
$name) {
            
$this->addLog("Getting all config");
            return 
$this->config;
        }
        if (!isset(
$this->config[$name])) {
            
$this->addLog("ERROR :: CONFIG '{$name}' does not exist");
            return 
null;
        }
        
$this->addLog("Getting config '{$name}' : " $this->config[$name]);
        return 
$this->config[$name];
    }

    function 
setConfig($name null$value null)
    {
        if (
$this->config == null) {
            
$this->initConfig();
        }
        
$this->config[$name] = $value;
        
$this->addLog("Setting config '{$name}' : " $this->config[$name]);
        return 
$this->config[$name];
    }

    function &
getHandler($name)
    {
        if (!isset(
$this->handler[$name '_handler'])) {
            
$this->initHandler($name);
        }
        
$this->addLog("Getting handler '{$name}'");
        return 
$this->handler[$name '_handler'];
    }

    function 
initModule()
    {
        global 
$xoopsModule;
        if (isset(
$xoopsModule) && is_object($xoopsModule) && $xoopsModule->getVar('dirname') == $this->dirname) {
            
$this->module $xoopsModule;
        } else {
            
$hModule xoops_gethandler('module');
            
$this->module $hModule->getByDirname($this->dirname);
        }
        
$this->addLog('INIT MODULE');
    }

    function 
initConfig()
    {
        
$this->addLog('INIT CONFIG');
        
$hModConfig xoops_gethandler('config');
        
$this->config $hModConfig->getConfigsByCat(0$this->getModule()->getVar('mid'));
    }

    function 
initHandler($name)
    {
        
$this->addLog('INIT ' $name ' HANDLER');
        
$this->handler[$name '_handler'] = xoops_getModuleHandler($name$this->dirname);
    }

    function 
addLog($log)
    {
        if (
$this->debug) {
            if (
is_object($GLOBALS['xoopsLogger'])) {
                
$GLOBALS['xoopsLogger']->addExtra($this->module->name(), $log);
            }
        }
    }
}

as you can see it is a very basic class just for initializing module handler, config, ...
but it will help a lot in other modules.
back to common.php you can see the developer create an object like this:
$debug false;
$publisher PublisherPublisher::getInstance($debug);

therefore in all other functions you can use it with ease.
eg:
$publisher->getHandler('category')->delete($categoryObj);


I repeat, it will help you a lot.
In xoops 26 there is a class called helper that will do the same job so by doing this in xoops255 and xoops256 modules you will be ready for the helper class.

Finally look at userlog/class/userlog.php as an another example.

6- deal with all request methods like $_GET, $_POST, $REQUEST, $_COOKIE,

prior to xoops26 each developer should deal with them. but in xoops 2.6 there is XoopsRequest class.
So what to do in xoops255 and xoops256?
there is a very simple solution thanks to core developer Trabis. in publisher there is a PublisherRequest class which you can copy/paste in your module with ease.
It is located at publisher/class/request.php

do these steps:
a) copy publisher/class/request.php to YOUR_MODULE/class/request.php

b) open the file and replace all "Publisher" with "Yourmodule"
pay attention to match case.
for example see in userlog/class/request.php

we will use request class in the future.

7- Create an standard table.

now back to our main deal. we want to create an standard table by fetching data from database and implement user side facilities like search, sort, order, pagination easily.

lets look at userlog/admin/logs.php because this module want to show a lot of data in a nice and easy format to read. Im sure you will see all of your issues and find their solutions in this example.

7-1- Where do we start ?
That is the question. what we want to show to the user when he click on the URI without any entry?
You should decide about these entries:
$startentry UserlogRequest::getInt('startentry',0);
$limitentry UserlogRequest::getInt('limitentry',$Userlog->getConfig("logs_perpage"));
$sortentry UserlogRequest::getString('sortentry','log_id');
$orderentry UserlogRequest::getString('orderentry','DESC');

As you can see by using the request class, we have an easy task to do. we just should get the data based on their types.
the first parameter is the name of entry in the URI.
the second parameter is the default value when entry is empty in URI

So for a given URI like this:
userlog/admin/logs.php?startentry=100&&limitentry=10&sortentry=log_time&orderentry=ASC

It will get the data from URI for all above entries.

clas request also can accept a third parameter for defining the request_method. the default for request method is $_REQUEST
$opentry UserlogRequest::getString('op''''post');


so in the above example we force it to get 'op' entry from $_POST method.

7-2- Criteria.
Any access to database needs a criteria. fortunately xoops core has a criteria class located in XOOPSCORE/class/criteria.php
you can use this class to create most usual criteria. So you can use this criteria to get data from database with START, LIMIT, SORT and ORDER

$logs $Userlog->getHandler('log')->getLogs($limitentry,$startentry,$criteria,$sortentry,$orderentry ,nullfalse);


and we told you that all access to database will be done thru module class. so look at function getLogs in userlog/class/log.php

$criteria = new CriteriaCompo();
        if (!empty(
$otherCriteria)) {
            
$criteria->add($otherCriteria);
        }
        
$criteria->setLimit($limit);
        
$criteria->setStart($start);
        
$criteria->setSort($sort);
        
$criteria->setOrder($order);
        
$ret $this->getAll($criteria$fields$asObject$id_as_key);


you can see how you can use criteria class.
At first you just need to define it.
then use some core functions like setSort.

The getAll function is the most important xoops function in working with database. you just need to use it to read any data.

7-3- Save entries and assign them to template.
we need to save all entries in the url for future usage in user navigation.
$GLOBALS['xoopsTpl']->assign('startentry'$startentry);
$GLOBALS['xoopsTpl']->assign('limitentry'$limitentry);
$GLOBALS['xoopsTpl']->assign('sortentry'$sortentry);
$GLOBALS['xoopsTpl']->assign('orderentry'$orderentry);


Also look at this:
// create query entry
$query_entry "&engine=" $engine "&limitentry=" $limitentry "&sortentry=" $sortentry "&orderentry=" $orderentry;

then some lines after that:
// query entry
$GLOBALS['xoopsTpl']->assign('query_entry', !empty($query_entry) ? $query_entry '');


So we can use it in template as an smarty variable.

7-4- page navigation:
At first you should have total count of data.
$totalLogs $Userlog->getHandler('log')->getLogsCount($criteria);

getLogsCount is a function that just count based on Xoops Core API getCount
so you can use getCount($criteria) in your module.
Then you should use the pagenav class to create a page navigation and assign it to template.
// pagenav to template
$pagenav = new XoopsPageNav($totalLogs$limitentry$startentry'startentry'$query_entry . (!empty($query_page) ? "&" $query_page ''));
$GLOBALS['xoopsTpl']->assign("pagenav", !empty($pagenav) ? $pagenav->renderNav() : '');


7-5- page navigation, sort, order in template.

look at userlog/templates/userlog_admin_logs.html

<{foreach item=title key=header from=$headers}>
            <
div title="<{$title}>" class="truncate width<{if $header == "admin" || $header == "pageadmin" || $header == "log_id" || $header == "uid" || $header == "item_name" || $header == "item_id"}>1<{else}><{$widthC}><{/if}> floatleft center">
                <
class="ui-corner-all tooltip" title="<{$title}>" href="logs.php?limitentry=<{$limitentry}>&sortentry=<{$header}><{if $query_page}>&<{$query_page}><{/if}><{if $sortentry eq $header}>&orderentry=<{if $orderentry eq 'DESC'}>ASC<{else}>DESC<{/if}><{/if}> " alt="<{$title}>"><{if $sortentry eq $header}><img src="<{if $orderentry eq 'DESC'}><{xoModuleIcons16 DESC.png}><{else}><{xoModuleIcons16 ASC.png}><{/if}>"/><{/if}><{$title}></a>
            </
div>
        <{/foreach}>


you just need to know some smarty.
As you can see for sort and order i use this if:
<{if $sortentry eq $header}>&orderentry=<{if $orderentry eq 'DESC'}>ASC<{else}>DESC<{/if}><{/if}>


it will find if user click on the header as sortentry. then if the order was not defined or it was ASC it will be DESC otherwise it will be ASC.

for showing an arrow. you just need another same if.
<{if $sortentry eq $header}><img src="<{if $orderentry eq 'DESC'}><{xoModuleIcons16 DESC.png}><{else}><{xoModuleIcons16 ASC.png}><{/if}>"/><{/if}>


pay attention that we will save the query_entry in every click.

for page navigation you just need to put the smart var any where you like
<{$pagenav}>


7-6- full div tables.

Only one question should be answered by you before writing full div table.
a) Is each row has a same height?
OR
b) every row should have the height of the tallest column in the table.

In userlog my answer is (a) so it is much simpler. for (b) you should look at newbb full div template set in newbb_thread.html template.

look at userlog/templates/userlog_admin_sets.html (userlog/templates/userlog_admin_logs.html used the same method but has more functionality)


userlog_admin_sets.html is more understandable for you.
<div class="head border x-small">
        <
div class="width1 floatleft center"><{$smarty.const._AM_USERLOG_SET_ID}></div>
        <
div class="width5 floatleft center"><{$smarty.const._AM_USERLOG_SET_ACTIVE}></div>
        <
div class="width10 floatleft center"><{$smarty.const._AM_USERLOG_SET_NAME}></div>
        <
div class="width5 floatleft center"><{$smarty.const._AM_USERLOG_SET_LOGBY}></div>
        <
div class="width5 floatleft center"><{$smarty.const._AM_USERLOG_SET_UNIQUE_ID}></div>
        <
div class="width40 floatleft center"><{$smarty.const._AM_USERLOG_SET_OPTIONS}></div>
        <
div class="width20 floatleft center"><{$smarty.const._AM_USERLOG_SET_SCOPE}></div>
        <
div class="truncate center"><{$smarty.const._EDIT}>|<{$smarty.const._DELETE}>|<{$smarty.const._AM_USERLOG_ADMENU_LOGS}></div>
        <
div class="clear"></div>
    </
div>


so for each row (here header row) you just need to define 2 nested divs.
the first div indicate the row:
<div class="head border x-small">

the second divs are columns.

In any column you should define a width and location (floatleft) until the last column. (floatleft can be defined in RTL languages as floatleft {float: right;})

width1, width5, width40 are defined width in xoops.css
Note: always try to use xoops.css styles at first. it will be consistent in all xoops and modules and all future versions.

In the last column we dont need to fix the width but we can use a truncate style to fit any long sentence to the row.
see in userlog/templates/css/style.css :
.truncate {
    
white-spacenowrap;
    
overflowhidden;
}
.
ellipsis {
    
text-overflowellipsis;  // IE 6+, FF 7+, Op 11+, Saf 1.3+, Chr 1+
}


Finally this line is very important:
<div class="clear"></div>


It will indicate the end of one row.
After finishing header, for other rows you can use a for loop
<{foreach item=set from=$sets}>
    <
div class="<{cycle values='even,odd'}> <{if $set.active eq 0}> deactive <{/if}> bold border">
        <
div class="width1 floatleft center"><{$set.set_id}></div>
        <
div class="width5 floatleft center"><{if $set.active eq 0}><{$smarty.const._MI_USERLOG_IDLE}><{else}><{$smarty.const._MI_USERLOG_ACTIVE}><{/if}></div>        
        <
div class="width10 floatleft center"><{$set.name}></div>
        <
div class="width5 floatleft center"><{$set.logby}></div>
        <
div class="width5 floatleft center"><{$set.unique_id}></div>
        <
div title="<{$set.options}>" class="width40 floatleft left"><{$set.options}></div>
        <
div title="<{$set.scope}>" class="width20 floatleft left"><{$set.scope}></div>
        <
div class="truncate ellipsis left">
            <
a href="setting.php?set_id=<{$set.set_id}>" title="<{$smarty.const._EDIT}>"><img src="<{xoModuleIcons16 edit.png}>" alt="<{$smarty.const._EDIT}>" title="<{$smarty.const._EDIT}>" /></a>
            |
            <
a href="setting.php?op=del&set_id=<{$set.set_id}>" title="<{$smarty.const._DELETE}>"><img src="<{xoModuleIcons16 delete.png}>" alt="<{$smarty.const._DELETE}>" title="<{$smarty.const._DELETE}>" /></a>
            |
            <
a href=
            <{if 
$set.unique_id eq 0}>
                
"logs.php"
            
<{elseif $set.logby eq $smarty.const._AM_USERLOG_UID}>
                
"logs.php?options[uid]=<{$set.unique_id}>"
            
<{elseif $set.logby eq $smarty.const._AM_USERLOG_SET_GID}>
                
"logs.php?options[groups]=g<{$set.unique_id}>"
            
<{elseif $set.logby eq $smarty.const._AM_USERLOG_SET_IP}>
                
"logs.php?options[user_ip]=<{$set.unique_id}>"
            
<{/if}>
                
title="<{$smarty.const._AM_USERLOG_ADMENU_LOGS}> - <{$set.logby}>=<{$set.unique_id}>" alt="<{$smarty.const._AM_USERLOG_ADMENU_LOGS}> - <{$set.logby}>=<{$set.unique_id}>" ><{$smarty.const._AM_USERLOG_ADMENU_LOGS}> - <{$set.logby}>=<{$set.unique_id}></a>            
        </
div>
        <
div class="clear"></div>
    </
div>
    <{/foreach}>


as you can see we can use ellipsis style in the last column to show a nice three dots (...) when the sentence is not fit.

A general note for theme/template designers: define an alignment for each column/row. It will be vitally important for RTL languages. so you can see in english it would be left { text-align: left;} but in persian it is left { text-align: right;}
the center show that it will be center in LTR and RTL.

2
irmtfan
Re: How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/4/28 7:18

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


I start by writing this tutorial.
but there are more information to add.
Finally we can have a good "how to write a standard module" document.
please add your notes.
especially designers can show us nice/easy ways.

3
Mamba
Re: How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/4/28 9:01

  • Mamba

  • Moderator

  • Posts: 11366

  • Since: 2004/4/23


Great start, Irmtfan! Thank you!

I also hope that others will contribute to it as well.

I am definitely planning to do so after our release of XOOPS 2.5.6 and the Basic Module Pack.
Support XOOPS => DONATE
Use 2.5.10 | Docs | Modules | Bugs

4
chefry
Re: How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/4/28 9:32

  • chefry

  • Home away from home

  • Posts: 1005

  • Since: 2006/10/14


If we are going to revise the FAQ, this should be in there

5
irmtfan
Re: How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/4/28 10:18

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


Honestly im not good at presentation
I hope some people can help and implement this tutorial into the XOOPS 2.5.5 Standard Module Structure Guidelines with more images.

for example when i wrote It is the last column in table with truncate and ellipsis, it is necessary to have an image of the last column. (An image can show the user what happens more than 1000 lines of description.)

Also we need to add a full tutorial for xoops_version.php but I thought we have something similar somewhere.

edit:
i add one pic
Resized Image

so you can see what will be the outcome of a full div table.



6
Mamba
Re: How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/5/1 8:01

  • Mamba

  • Moderator

  • Posts: 11366

  • Since: 2004/4/23


Once we finish the Basic Module Pack, we'll need to focus more on standardization and more re-use between the modules.
Support XOOPS => DONATE
Use 2.5.10 | Docs | Modules | Bugs

7
irmtfan
Re: How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/5/1 12:19

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


Ok. today i add these about xoops_version.php to the first post:

some of them are really useful for cloning modules.
Quote:

see an example of xoops_version.php from publisher.
So we can review them one by one:
- Note: many of the following will be shown in module > about page.
// Name of the module
$modversion['name'] = _MI_PUBLISHER_MD_NAME;

- You should get the name from language file.

// Version of the module
$modversion['version'] = 1.0;


- for versioning please pay attention that xoops core can just accept this format:
VERSION = YYYY.XX
So it will ignore any version after the second number after point.
so these versions are same for xoops core:
1.011
1.014
OR:
12.126
12.129

So if your module needs an upgrade you should change the first or second number after point.

// Brief description of the module
$modversion['description'] = _MI_PUBLISHER_MD_DESC;
// Author
$modversion['author'] = "Xuups.com";
// Credits - other people but not shown in about page
$modversion['credits'] = "w4z004, hsalazar, Mithrandir, fx2024, Ackbarr, Mariuss, Marco, Michiel, phppp, outch, Xvitry, Catzwolf, Shine, McDonald, trabis, Mowaffak, Bandit-x, Shiva";
// the arg of the help url, If your module have a help file
$modversion['help']  = 'page=help';
// License
$modversion['license'] = 'GNU GPL 2.0';
$modversion['license_url'] = "www.gnu.org/licenses/gpl-2.0.html/";
// If it’s an official module = 1 but usually it is not
$modversion['official'] = 0;


// Name of the directory that holds the module.
$modversion['dirname'] = basename(__DIR__);

- use basename(__DIR__) for better cloning.

// Path and name of the module’s logo
$logo_filename $modversion['dirname'] . "_logo.png";

if (
file_exists(XOOPS_ROOT_PATH "/modules/" $modversion['dirname'] . "/images/" $logo_filename)) {
    
$modversion['image'] = "images/{$logo_filename}";
} else {
    
$modversion['image'] = "images/module_logo.png";
}

- While you could simply add your logo like this:
// Path and name of the module’s logo
$modversion['image'] = "images/mymodule_logo.png";

The above from publisher is the best way for cloning.
MORE TO COME xoops_version.php

8
redheadedrod
Re: How to write an standard module for xoops (div table, pagination , sort, order)

Please correct me if I am wrong but I believe you have one error here...

This code I believe is in error unless I am mistaken:
$modversion['name'] = _MI_PUBLISHER_MD_NAME;


This variable is used as the name of the module and is used any time the module is accessed. In the manner you show it here it would use a language constant and would try to access the module by that name which would then error out when being accessed by any language other than the default language it was installed under.

With this in mind if we instead use:
$modversion['name'] = basename(__DIR__);


Or even one step further:
$moduleName basename(__DIR__);
$modversion['name'] = $moduleName;


Then our module becomes automatically clonable as long as we always make use of the $moduleName instead of hard coding the name in our module code.

This $moduleName may have to be included in a "include" file that is included in every page of the module instead of the xoops_version.php. I can't remember if modules normally include the xoops_version.php page or not.

When taking cloning into consideration one should have the name of the module in one location to make it simple to change and not make it hard coded. By using the directory name as the name of the module you make it much simpler to clone. This way it is as simple as renaming the directory before installing the module. Regardless if you do it in the xoops_version.php or elsewhere please always make a constant or a variable to hold the name of the module and use this in all locations to access anything to do with the module. This will prevent someone from having to run a "clone" routine to clone the module.

All future modules I build from scratch or redo will use this method to allow full cloning by simple renaming of the directory.

One note to make that may be considered here. (I have NOT looked at publisher to see if Trabis is doing it this way.)

The following line may work:
$modversion['name'] = _MI_PUBLISHER_MD_NAME;


If the language files contain a line like this:
define(_MI_PUBLISHER_MD_NAMEbasename(__DIR__));


Then you are setting a constant to the current directory name. Using a constant is much nicer than using a variable because it has little to no overhead and uses less memory as well. I am not sure however if you can define a constant with a name derived from a method like this so you may be limited to the $moduleName way.

With this in your code then you would use this constant instead of the $moduleName as I mentioned above. However you may want to shorten the name to prevent having to type in such a long name every time you want to use the module name.

9
irmtfan
Re: How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/5/2 3:16

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


yes your are wrong.
// Name of the module
$modversion['name'] = _MI_PUBLISHER_MD_NAME;


is just the name of the module in user own language and be used just for displaying purposes.
so for example in english it is publisher and in persian it is پابلیشر

you can have 10 clone of the module working with one name publisher

the important part for cloning is dirname:
// Name of the directory that holds the module.
$modversion['dirname'] = basename(__DIR__);

- use basename(__DIR__) for better cloning.

Anyway, IMO the most important module that should follow this standard is TDMCreate as the xoops standard module creator.
Now it will not implement many of these standards. eg:
$modversion['dirname'] = basename(__DIR__);

edit:
Quote:

All future modules I build from scratch or redo will use this method to allow full cloning by simple renaming of the directory.

As you can see i wrote that all module developers should have a file include/common.php and put all needed definitions like this:
defined("XOOPS_ROOT_PATH") or die("XOOPS root path not defined");

define("PUBLISHER_DIRNAME"basename(dirname(__DIR__)));
define("PUBLISHER_URL"XOOPS_URL '/modules/' PUBLISHER_DIRNAME);
define("PUBLISHER_IMAGES_URL"PUBLISHER_URL '/images');
define("PUBLISHER_ADMIN_URL"PUBLISHER_URL '/admin');
define("PUBLISHER_UPLOADS_URL"XOOPS_URL '/uploads/' PUBLISHER_DIRNAME);
define("PUBLISHER_ROOT_PATH"XOOPS_ROOT_PATH '/modules/' PUBLISHER_DIRNAME);
define("PUBLISHER_UPLOADS_PATH"XOOPS_ROOT_PATH '/uploads/' PUBLISHER_DIRNAME);

xoops_loadLanguage('common'PUBLISHER_DIRNAME);

include_once 
PUBLISHER_ROOT_PATH '/include/functions.php';
include_once 
PUBLISHER_ROOT_PATH '/include/constants.php';
include_once 
PUBLISHER_ROOT_PATH '/include/seo_functions.php';
include_once 
PUBLISHER_ROOT_PATH '/class/metagen.php';
include_once 
PUBLISHER_ROOT_PATH '/class/session.php';
include_once 
PUBLISHER_ROOT_PATH '/class/publisher.php';
include_once 
PUBLISHER_ROOT_PATH '/class/request.php';

$debug false;
$publisher PublisherPublisher::getInstance($debug);

So you can use PUBLISHER_DIRNAME anywhere.
But there are other serious obstacles for cloning.
1- mysql.sql have hard-coded tables and you cannot do anything because xoops core will read it in install.

2- you have class names like
class MY_MODULE_classname extends XoopsObject
{

and it will cause issues when you have 2 blocks of the all 2 clones in one page:
Fatal errorCannot redeclare classMY_MODULE_classname in ...

There maybe other obstacles that i cannot remember. but anyway by following this standard the module will be ready for cloning.
Anyway i think this topic is not a good place to talk about cloning. it is better to continue in another topic.

10
redheadedrod
Re: How to write an standard module for xoops (div table, pagination , sort, order)

Quote:

irmtfan wrote:
yes your are wrong.
// Name of the module
$modversion['name'] = _MI_PUBLISHER_MD_NAME;


is just the name of the module in user own language and be used just for displaying purposes.
so for example in english it is publisher and in persian it is پابلیشر

you can have 10 clone of the module working with one name publisher

Ok modules I have looked at used this as the name of the module to do other things as well and wasn't used like this so I guess you can say I was confused...

Thought it was used as the "name" of the module used for things such as how it was saved in the database and otherwise.
Come to think of it I think all of the modules I have looked at this property of the name and directory were the same.

Login

Who's Online

206 user(s) are online (119 user(s) are browsing Support Forums)


Members: 0


Guests: 206


more...

Donat-O-Meter

Stats
Goal: $100.00
Due Date: Mar 31
Gross Amount: $0.00
Net Balance: $0.00
Left to go: $100.00
Make donations with PayPal!

Latest GitHub Commits