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

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


Quote:

Ok modules I have looked at used this as the name of the module to do other things as well

strange!
the dirname is used for many jobs like loading the handler and ... but name should not be used. it can be anything even a long sentence with many spaces so it will break module in multi-bytes translations.

Today i add these for xoops_version.php
Quote:


// 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

//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';
$modversion['icons16']          = 'Frameworks/moduleclasses/icons/16';
$modversion['icons32']          = 'Frameworks/moduleclasses/icons/32';


The above settings will be used in about/index page.
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

// 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"



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

As I said, the Directory and Name variables were the same value so that is probably where I got confused. They may have used the Name value in place of the directory value and since they were the same it worked.

Also, you may want to check but I believe as of 2.6 the OnInstall and OnUpdate scripts are no longer a part of xoops. I seem to recall there was mention of their removal and they were being replaced with a method that Trabis was going to document later.

We need "Depends" and "Links" values added to core so we can use dependencies and if modules can use functions from one another or are "linked".

13
irmtfan
Re: How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/5/5 8:42

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


if a module has the same name and dirname then it has hard-coded name. Maybe a module used name in some dirty ways in its own codes but in the core scope just dirname will be used

Quote:

as of 2.6 the OnInstall and OnUpdate scripts are no longer a part of xoops.

You are nearly right. but we still have those scripts in 2.6 ( for legacy support) but there is a new preload for them.

but this tutorial is for 2.5.6 (with the most possible compatibility with 2.6)

but OnInstall/OnUpdate/OnUninstall preloads are just defined in 2.6

read more here:
https://xoops.org/modules/newbb/viewtopic.php?post_id=353436#forumpost353436

14
zyspec
Re: How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/5/12 21:54

  • zyspec

  • Module Developer

  • Posts: 1095

  • Since: 2004/9/21


@irmtfan, et al.

I know this thread has been around a little while but after I looked it over more thoroughly with the intent that this could be used as a XOOPS standard. I'd like to re-open a discussion about recommending the usage of the magic __call() method.

I have a concern in the use/creation of __call as a 'recommended' practice.

/**  
     * @param string $method  
     * @param array  $args  
     *  
     * @return mixed  
     */  
    
public function __call($method$args)  
    {  
        
$arg = isset($args[0]) ? $args[0] : null;  
        return 
$this->getVar($method$arg);  
    }


I was of the belief that this was a good thing but after reviewing numerous expert opinions, etc. I now believe that these should not be used, and definitely not recommended, as a XOOPS standard. I think magic functions can be useful in closed system (or used by a small group of experienced programmers) but not in an open system like XOOPS with varying levels of users/programmers.

We started a discussion about 6 months ago about using __call() - seehttps://xoops.org/modules/newbb/viewtopic.php?post_id=349435. After that discussion, and some time researching further, I've come to believe we should not encourage magic method usage.

Thoughts?

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

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


Good.
yes i read that interesting topic before.
I agree to remove that part but anyway it help me a lot to reduce code.
eg: for time i wrote this:
public function log_time()
    {
        return 
$this->userlog->formatTime($this->getVar('log_time'));
    }

so if i need formatted time i use $Obj->log_time() but if i need the timestamp i use $Obj->getVar('log_time')

there are many interesting usage of magic function like this:
http://stackoverflow.com/questions/356128/can-i-extend-a-class-using-more-than-1-class-in-php

But i agree to remove this part from the standard tutorial.
Please contribute more. we really need opinions from experts.


16
bumciach
Re: How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/5/24 12:09

  • bumciach

  • Not too shy to talk

  • Posts: 153

  • Since: 2007/6/25


Nice job.

How about joining tables?

Quote:

irmtfan wrote:
Then you should create a class for each table and in the future any access to database will be done by using those classes.


For example: displays list articles with category names.

Easy way:
1 - read articles from database by using article class
2 - for every article object do access to database by using category class
cons: duplicating queries to database - the more articles, the more requests for categories

In XoopsObject class there is getByLink() method. Using this method, we can build a query to the joined tables (by joint handlers of two classes). But it is difficult way, especially if we join more than two tables.

In more complex tasks I do this (simplified example):


class mymoduleArticleHandler extends XoopsPersistableObjectHandler
{
  function &
getObjects($args)
  {
      
$sql 'SELECT t2.name AS category_name, t1.* FROM mymodule_article t1, LEFT JOIN mymodule_category t2 ON [...]'//need add initVar('category_name') to Article class 
      
      
if($array $this->db->fetchArray($this->db->query($sql))){
            
$article $this->create(false); //for every record from db we create article Object; we don't preparing the new object to insert to database, so $isNew Flag the new objects must be false
          
$article->assignVars($array); // 'fill' Article object with data from joined tables
          
$ret[] = $article//collect every object to $ret array
      
}
      
      return 
$ret;
  }
}

17
irmtfan
Re: How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/5/25 5:39

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


Hi bumciach,
Im glad that you liked this topic.
Really the main goal of this "how to write a standard module" would be guidelines for writing a strong, understandable, easy to debug and consistent base codes for all xoops modules.
Therefore because this standard has to be basic I did not add special needs (eg: special queries like joining).

but if a developer follow this guideline the output module is ready for adding any special needs too.
for example for joining purposes, the most important thing is not adding a hard-coded table name
And if the developer followed this guideline:
1- If module had one class for each table we should use handlers to find/use table name and avoid hard-code.
2- If module used helper class it will be easier too

So for example to join (of course you dont need join which I will describe later) we can improve your query:
class mymoduleArticleHandler extends XoopsPersistableObjectHandler 

  function &
getObjects($args
  { 
      
$sql "SELECT t2.name AS category_name, t1.* FROM {$this->table} t1, LEFT JOIN {$this->MODULE_X->getHandler('category')->table} t2 ON t2.category_id = t1.category_id"//need add initVar('category_name') to Article class

and $this->MODULE_X is the helper class for module x.(module x can be the module itself)
The above is without hard-code and very understandable for everybody.

Then I like to discuss about your special need and your exampled code(which is off-topic so maybe it is better to be in another topic ):
Side note 1:
I dont know these codes you wrote comes from what module? but they are not good. One can do this without JOIN by enhancing that Easy way
Quote:

For example: displays list articles with category names.

Easy way:
1 - read articles from database by using article class
2 - for every article object do access to database by using category class

why do you (or that module developer) think you cannot follow that easy way more effective and with much less codes than the JOIN way?
It can be done just by two queries.
1 - read articles from database by using article handler class
SELECT FROM {$this->table}

2- for all above article category Ids do access to database by using category handler class.
SELECT category_idcategory_name FROM {$this->tableWHERE category_id IN {$articles_category_id}


which $articles_category_id is all category ids you get from the first query.

To implement the above you even dont need to write special function (function &getObjects($args))

All can be done in front side
I commented the below code for you to see how it can be done with ease
$articleH xoops_getmodulehandler('article''MODULE');
$categoryH xoops_getmodulehandler('category''MODULE');
$criteriaArticle = new CriteriaCompo();
//  continue your criteria here for $criteriaArticle
// ...

// START first query for articles
// remember getAll($criteria = null, $fields = null, $asObject = true, $id_as_key = true)
$articleObjs $articleH->getAll($criteriaArticle);
// END first query for articles

$articles_category_id = array();
foreach (
$articleObjs as $artObj) {
    
$articles_category_id[] = $artObj->getVar("category_id");
    
// you can do any other thing here with articles!!!
    // ...
}
// START second query for category names
$criteriaCat = new CriteriaCompo();
$criteriaCat->add(new Criteria("category_id""(" implode(", "$articles_category_id) . ")""IN"), "AND");
// you can add what fields you need here :D
$catFields = array("category_id","category_name");
$catNames $categoryH->getAll($criteriaCat$catFieldsfalse);
// END second query for category names

// FINISH!!!
// usage: $catNames[$artObj->getVar("category_id")]["category_name"]

As i said before the above code is:
1- most effective code to do the job (more than JOIN) 2 simple queries are better than one query with JOIN.
2- no need to write any code in the handler class
3- can be extend very easy. eg: if you need category_created time you can add the field.

Side note 2:
Anyway IMO your query will not work because you didnt add db prefix:
instead of:
mymodule_article

you have to use:
$this->db->prefix('mymodule_article')

but using hadlers and helper class you will not need to think about such a annoying thing (like i must add prefix here or not)

Side note 3:
If you are very tight for using only one query you should add one field category_name to articles table.
//need add initVar('category_name') to Article class

As the above comment after your query suggested, If you really need to display the category name alongside an article in many links of your module it is better to add another field called "category_name" in mymodule_article table.
But generally you dont need that and i dont advise it


18
bumciach
Re: How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/5/27 9:55

  • bumciach

  • Not too shy to talk

  • Posts: 153

  • Since: 2007/6/25


Quote:

irmtfan wrote:
Therefore because this standard has to be basic I did not add special needs (eg: special queries like joining).


I know. But I think those needs are rather common. So it would be good to mention something about join queries or best practise how use handlers/helpers to get data from two and more tables from DB. Even in the basic guideline.

Quote:
the most important thing is not adding a hard-coded table name


You are right. As I said, my example was very simplified. I have wrote this code from scratch in a minute (but I use similar in my modules, but none of them are ready enought to publish and are very very specific). So I didn't bother to write a complete query, but how to handle data. Sorry, I should have wrote more accurate code in example to be more clear.

I keep to the principle that even if I have to put a SQL query in code explicitly, do it only in the handler classes. Database query shouldn't be in many files.

Back to examples. How is more efficient? IMO It all depends on your specific requirements. If I have to display at once interconnected data from more than two tables, and reuse it in many places. For me, simpler is to add more code to the handler class to handle the join query. I think there is no big problem that one of the handlers do extra job.

Quote:
If you are very tight for using only one query you should add one field category_name to articles table.

I mean add variable initialized for the object (mymoduleArticles) not to add field into table (articles) in database.

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

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


Ok. Today i finally finish the xoops_version.php tutorial.
I add the following to the first post:
Quote:


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 ==


Also I remove the magic function __call

And I add a brief statement to guide using another table/handler in one handler by using the module helper class. (eg: join purposes)
Quote:

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.


@bumciach:
AFAIK developers should avoid JOIN as much as possible.
But anyway i add the above tutorial.

I am sure 2 simple queries is better than one JOIN.

but I am not sure about that when tables are more than 2 like 3, 4, 5 or more.

somebody with more information could tell us.

Quote:

I mean add variable initialized for the object (mymoduleArticles) not to add field into table (articles) in database.

Then it will not help you to reduce queries.*/

20
bumciach
Re: How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/5/29 8:41

  • bumciach

  • Not too shy to talk

  • Posts: 153

  • Since: 2007/6/25


Quote:

irmtfan wrote:

@bumciach:
I am sure 2 simple queries is better than one JOIN.

but I am not sure about that when tables are more than 2 like 3, 4, 5 or more.


if I need to get data from only two tables then, in most cases I use the method you described.
In my experience. The more tables including the use of JOIN is simpler (less code). But I did not do benchmarks.


Quote:

Then it will not help you to reduce queries.


I had something else in mind

Suppouse we have two tables in db
CREATE TABLE mod_mymodule_articles (
  
article_id int(11unsigned NOT NULL auto_increment,
  
category_id mediumint(8unsigned NOT NULL default 0,
  
title varchar(50NOT NULL default '',
  
bodytext TEXT NOT NULL,
  
PRIMARY KEY  (article_id)
);
CREATE TABLE mod_mymodule_categories (
  
category_id mediumint(8unsigned NOT NULL auto_increment,
  
name varchar(50NOT NULL default '',
  
descr TEXT NOT NULL,
  
PRIMARY KEY  (category_id)
);


So...

class mymoduleArticleHandler extends XoopsPersistableObjectHandler 

  function &
getObjects($args
  { 
      
$sql "SELECT t2.name AS category_name, t1.* FROM {$this->table} t1, LEFT JOIN {$this->MODULE_X->getHandler('category')->table} t2 ON t2.category_id = t1.category_id";


We get records from DB by handler, but we access to them by object mymoduleArticle class

class mymoduleArticle extends XoopsObject
{
    
/**
     * initialize variables for the object
     */
    
function __construct()
    {
        
$this->initVar("article_id"XOBJ_DTYPE_INTnullfalse);
        
$this->initVar("category_id"XOBJ_DTYPE_INTnullfalse);
        
$this->initVar("title"XOBJ_DTYPE_TXTBOXnullfalse);
        
$this->initVar("bodytext"XOBJ_DTYPE_TXTBOXnullfalse);
        
//additional fields eg from other tables
        
$this->initVar("category_name"XOBJ_DTYPE_TXTBOXnullfalse); //this is need to get value from JOIN query 'SELECT t2.name AS category_name [...]'                
    
}
}


Usage:

$articles $article_handler->getObjects$criteria );
foreach (
$articles as $art) {
  echo 
$art->getVar('title');
  echo 
$art->getVar('category_name'); 
}


This is useful especially if in many places (and even in other modules) to get a list of titles with the name of the category. Note that in this scope we don't need all fields from joined table (mod_mymodule_categories).
In other case problably more accurate will be reference to both class (mymoduleArticle and mymoduleCategory) - you still have mymoduleCategory class with defined fields, so does not make sense to duplicate those fields in the mymoduleArticle class.

Login

Who's Online

197 user(s) are online (92 user(s) are browsing Support Forums)


Members: 0


Guests: 197


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