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.sqlIt 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 tutorial3-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#forumpost3496973-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.sqlhere 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` (
`categoryid` int(11) NOT NULL auto_increment,
`parentid` int(11) NOT NULL default '0',
`name` varchar(100) NOT NULL default '',
`description` text NOT NULL,
`image` varchar(255) NOT NULL default '',
`total` int(11) NOT NULL default '0',
`weight` int(11) NOT NULL default '1',
`created` int(11) NOT NULL default '1033141070',
`template` varchar(255) NOT NULL default '',
`header` text NOT NULL,
`meta_keywords` text NOT NULL,
`meta_description` text NOT NULL,
`short_url` varchar(255) NOT NULL default '',
`moderator` int(6) NOT 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.phpyou 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_INT, null, false);
$this->initVar("parentid", XOBJ_DTYPE_INT, null, false);
$this->initVar("name", XOBJ_DTYPE_TXTBOX, null, true, 100);
$this->initVar("description", XOBJ_DTYPE_TXTAREA, null, false, 255);
$this->initVar("image", XOBJ_DTYPE_TXTBOX, null, false, 255);
$this->initVar("total", XOBJ_DTYPE_INT, 1, false);
$this->initVar("weight", XOBJ_DTYPE_INT, 1, false);
$this->initVar("created", XOBJ_DTYPE_INT, null, false);
$this->initVar("template", XOBJ_DTYPE_TXTBOX, null, false, 255);
$this->initVar("header", XOBJ_DTYPE_TXTAREA, null, false);
$this->initVar("meta_keywords", XOBJ_DTYPE_TXTAREA, null, false);
$this->initVar("meta_description", XOBJ_DTYPE_TXTAREA, null, false);
$this->initVar("short_url", XOBJ_DTYPE_TXTBOX, null, false, 255);
$this->initVar("moderator", XOBJ_DTYPE_INT, null, false, 0);
//not persistent values
$this->initVar("itemcount", XOBJ_DTYPE_INT, 0, false);
$this->initVar('last_itemid', XOBJ_DTYPE_INT);
$this->initVar('last_title_link', XOBJ_DTYPE_TXTBOX);
$this->initVar("dohtml", XOBJ_DTYPE_INT, 1, false);
}
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
* @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.phpdo 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.phpwe 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 ,null, false);
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">
<a 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-space: nowrap;
overflow: hidden;
}
.ellipsis {
text-overflow: ellipsis; // 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.