Module CustomFields Dev

From Dolibarr ERP CRM Wiki
Revision as of 15:40, 29 August 2012 by Lrq3000 (talk | contribs)
Jump to navigation Jump to search
CustomFields
Numero/ID of module 8500
User doc. of module Module_CustomFields
Developer doc. of module This page

These notes are aimed to developpers who want to extend the functionnalities of the module or want to use it for extreme cases. This is not meant to guide implementers on how to implement and configure the module (see the related chapter for that).

If you are an implementer and just want to use CustomFields with its normal functions, you can read the user documentation of CustomFields.

Principles

CustomFields is simply a wrapper for SQL databases.

The module is based on SQL to manage the fields and most of the stuff. It uses and understands standard SQL.

The main principle of CustomFields is:

  • one SQL field = one Dolibarr field
  • one SQL type = one method to show and edit the field in Dolibarr

Goals

This module was made to provide an easy way to manage custom sql fields, not only for users and implementers, but also for developpers to use in their own module:

  • by easily adding the support of custom fields for your own module by using this module;
  • by reusing custom fields from another module in your own module;
  • by managing sql fields in your own module by using the CustomFields class (which will take care of everything from printing, editing to PDF/ODT templating).

So this module is not only an auxiliary of other modules, but it can also be the core part of your own module by using it as your SQL query class for fields.

SQL structure

A CustomFields table needs to respect three parameters that never change:

  • table's name (format: llx_*dolibarrmodule*_customfields where *dolibarrmodule* = table_element in conf_customfields.lib.php)
  • first column: rowid, which is the primary key for each field
  • second column: fk_*dolibarrmodule* which is the foreign key that links every customfields record to the Dolibarr's module that is associated (invoice, order, etc...).

Apart from the table's name and the first two special columns, every other columns are just standard sql columns that you can freely create, edit or delete as you want, from the module's administration page or from a third-party SQL management tool (such as phpMyAdmin).

You can also create more complex sql fields: constraints, foreign keys, transformations, other sql types, etc...).

Then the module will try to manage the sql fields you manually created the best it can:

  • by printing appropriate input fields in the Dolibarr gui interface.
  • by providing the developper an easy access to these fields in php (eg: $customfields->cf_myfield or $object->customfields->cf_myfield) for any use, including PDF templates and ODT templates, or in any php script (so that you can use these fields in your own module).

SQL Indexes

The module automatically adds an index on the primary key and foreign key (first and second columns).

To further optimize the performances of your database, and particularly the customfields tables, you may want to manually add indexes based on usage.

Also, you may add indexes on remote tables used by constrained fields, which could optimize even more the performances since constrained fields have the worst performance drawback for CustomFields.

Architecture

Here is a full list of the CustomFields packaged files with a short description (for a more in-depth view just crawl the source files, they are full of comments):

Core files

files that are necessary for the CustomFields to work, they contains the core functions

  • /customfields/admin/customfields.php --- Administrator's configuration panel : this is where you create and manage the custom fields definitions
  • /customfields/class/actions_customfields.class.php --- Hooks class : used to hook into Dolibarr core modules without altering any core file (can be used to hook into your own modules too)
  • /customfields/class/customfields.class.php --- Core class : every database action is made here in this class. You can find some printing functions because they are very generic.
  • /customfields/conf/conf_customfields.lib.php --- Configuration file : contains the main configurable variables to adapt CustomFields to your needs or to expand its support to other modules and more native sql types.
  • /customfields/conf/conf_customfields_func.lib.php --- Auxiliary functions library to read and manage config variables
  • /customfields/core/modules/modCustomFields.class.php --- Dolibarr's module definition file : this is a core file necessary for Dolibarr to recognize the module and to declare the hooks to Dolibarr (but it does not store anything else than meta-informations).
  • /customfields/core/substitutions/functions_customfields.lib.php --- CustomFields substitution class for ODT generation : necessary to support customfields tags in ODT files
  • /customfields/core/triggers/interface_50_modCustomFields_SaveFields.class.php --- Core triggers file : this is where the actions on records are managed. This is an interface between other modules and CustomFields management. This is where you must add the actions of other modules you'd want to support (generic customfields triggers actions are provided so you just have to basically do a copy/paste, see the related chapter). Also, if CustomFields are shown in the forms but cannot be modified nor saved, then probably this trigger file is not detected by Dolibarr (maybe you can try to lower the number 50 to raise the priority?).
  • /customfields/fields/customfields_fields_extend.lib.php --- Overloading functions library with users functions
  • /customfields/langs/code_CODE/customfields.lang --- Core language file : this is where you can translate the admin config panel (data types names, labels, descriptions, etc.)
  • /customfields/langs/code_CODE/customfields-user.lang --- User defined language file : this is where you can store the labels and values of your custom fields (see the related chapter)
  • /customfields/lib/customfields_aux.lib.php --- Simplifier library to easily use CustomFields class to populate an $object with custom fields datas (Facade design pattern)
  • /customfields/lib/customfields_printforms.lib.php --- Core printing library for records : contains only printing functions, there's no really core functions but it permits to manage the printing of the custom fields records and their editing
  • /customfields/sql/*.sql --- Unused (the tables are created directly via a function in the customfields.class.php)

PDFTest module

optional module to append a page listing all custom fields values on newly generated PDF documents. To be used to test if custom fields are working correctly before making one's own template.

  • /customfieldspdftest/class/actions_customfieldspdftest.class.php --- Main functions library and hooks class, to hook onto pdf generation. Contains the PDFTest functions.
  • /customfieldspdftest/core/modules/modCustomFieldsPDFTest.class.php --- Module descriptor for Dolibarr

How to add the support of a new module

There are two cases: either the module already implements hooks, and you just have to configure CustomFields to support it, or either the module does not implements hooks nor triggers.

This chapter will guide you through the steps to make any module supported by CustomFields (be it a Dolibarr core module or your own module).

Preliminary test

This preliminary test will allow you to know at a glance how much work needs to be done in order to make your module supported by CustomFields, and it will also give you all the config parameters if they are available.

Important note: this test will only give data about hooks, which is the main part to make CustomFields work for your module. But for triggers, there is no way to test, so you will have to check the triggers manually (either by testing if the custom fields are saved at creation page, or by looking at the source code of the objects classes).

Edit the php script of the module (check the URL when you access the module's datasheet or creation page - attention both are two different pages, so you have to do this twice), then put this at the very end of the php script:

// CustomFields debuglines to show config parameters for current module
print("<pre>"); // html nice output
print("table_element: {$object->table_element} - table_element_line: {$object->table_element_line} - contexts: ");
print_r($hookmanager->contextarray);
print(" - conf hooks_modules: ");
print_r($conf->hooks_modules);
print(" - hooks attached: ");
print_r($hookmanager->hooks);
print(" - user rights: ");
print_r($user->rights);
print("</pre>");

Now reload the page, and scroll at the very bottom. You should see some informations about the module.

Depending on the data available, you will know what you have to do:

  • If there's no table_element: $object is not defined, try to find how is called the main object variable and replace $object by this variable in the debug code given above (eg: in Agenda, the object variable is called $agenda and $agendacomm).
  • If there's no table_element_line: either there's no products lines for this module, either the main object variable is not called $object (see the previous point). One last case is that the object is not standard and does not store table_element_line (the name of the table that stores its products lines), in which case you have to manually add it in the class (eg: $this->table_element_line = 'something').
  • If there's no contexts: the hookmanager is not defined, and probably there's no hook at all in this module, so you'll have to implement them by yourself. Jump to Module_CustomFields_Dev#Hooks.
  • If there is a list of contexts but the module cannot be found in the list: the hookmanager is defined but the module has not defined a context, you still have to implement the hooks. Jump to Module_CustomFields_Dev#Hooks.
  • If there's no conf hooks_modules
  • If there is no $user->rights: there is either no standard security implemented in this module, or either the variable is called with another name. If there is no security, you don't need to configure it in CustomFields, but if there is a security, you need to find how the variable is called (like $object can get another name sometimes).

When you are finished with implementing the support of CustomFields in the module, you should delete these debug lines.

Implementing the support of CustomFields in the module

If there is a module you want to implement the support of CustomFields, but does not yet has the required components, here is a guide on how to implement these components.

Hooks

Implementing hooks will allow custom fields to appear on your module's page. They will also be editable and they will save the data. For creation, you need to use a trigger (see the subchapter below).

First you should be familiar with the way that hooks are implemented in modules. If you don't, please first read the hooks documentation.

An example case of implementation of hooks into a Dolibarr core file can be found in the CustomFields Example Cases wiki page.

Datasheet hook

To implement CustomFields in your module, you must initialize the hook manager, then use the following hook context:

  • formObjectOptions

This hook must be placed everywhere in your code where you want custom fields to appear (eg: on the creation page and datasheet view page).

Note: Generally, you will have to place the hook just before the end of a table tag (), so if you don't know where to place it (eg: if you're implementing CustomFields on a Dolibarr core module), just search for this tag in the page shown in the url (eg: /htdocs/commande/fiche.php).

Eg: First load the hookmanager at the beginning of your php script:

// Initialize technical object to manage hooks of thirdparties. Note that conf->hooks_modules contains array array
include_once(DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php');
$hookmanager=new HookManager($db);
$hookmanager->initHooks(array('yourmodule'));

Then place the hook as many times as required:

// ... a lot of php code ...

print '</td></tr>'; // end of the last row of a table

// Other attributes
$parameters=array('colspan' => ' colspan="3"'); // specifying the colspan is optional but will help CustomFields know how to correctly print the table
$reshook=$hookmanager->executeHooks('formObjectOptions',$parameters,$object,$action);    // Note that $action and $object may have been modified by hook

print '</table>'; // end of the table, this is what you're looking for, place the hook just above

// ... a lot of php code ... redo the same if you find another </table> and there's a page where there's no custom fields but you want to place custom fields on there

Note2: If after implementing the hook you receive an error or custom fields don't appear, you may have to change $object to another variable, depending on what is called the main object (eg: in Agenda it's called $action and $actioncomm).

Lines hook

For product lines, you should anyway use the product lines template files: htdocs/core/tpl/freeproductline_edit.tpl.php

If you don't, you can still manually implement the required hooks, what is required is that you supply $line (which is the current line object being processed) in the $parameters array:

$parameters=array('line'=>$line,'fk_parent_line'=>$line->fk_parent_line);

echo $hookmanager->executeHooks('formEditProductOptions',$parameters,$this,$action);

Hooks names:

  • formEditProductOptions
  • formCreateProductOptions ($line is not required here, because we create a new product line! you can leave $parameters=array(); empty).

Note: there's no hook for viewing, because showing the customfields will clutter the visual field, but if you really want you can also do it by adding a formViewProductOptions hook.

Triggers

First you need to know how to implement triggers in Dolibarr. For more informations about how to implement triggers in your module, please read the triggers wiki page.

An example case of implementation of hooks into a Dolibarr core file can be found in the CustomFields Example Cases wiki page.

Implementing triggers on your module will allow CustomFields to save the fields at creation (datasheet edition is already managed by hooks).

You need to implement 3 triggers: - on creation of object - eg: YOURMODULE_CREATE - on creation of lines - eg: YOURMODULELINE_INSERT (optional, only if your module has product lines) - on update of lines - eg: YOURMODULELINE_UPDATE (optional, only if your module has product lines)

Note: you need to supply the sql id of the newly created object before calling the trigger.

Eg:

$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."yourmodule_table_element");

// Appel des triggers
include_once(DOL_DOCUMENT_ROOT . "/core/class/interfaces.class.php");
$interface=new Interfaces($this->db);
$result=$interface->run_triggers('YOURMODULE_CREATE',$this,$user,$langs,$conf);
if ($result < 0) {
    $error++; $this->errors=$interface->errors;
}
// Fin appel triggers

Note2: if it's a Dolibarr core module, you can easily find the right place to put the trigger by searching for 'create sql' in the class subfolder (eg: URL is /htdocs/commande/fiche.php, the class is in /htdocs/commande/class/commande.class.php).

Note3: CustomFields also supports a few other triggers like _CLONE, _UPDATE, _MODIFY, but they are not required and may not work depending on how your module works to manage these actions.

Configuring CustomFields to support the module

Preliminary work

You need to get the parameters of this module, and you will then put them in the CustomFields configuration file (conf_customfields.lib.php).

Getting the parameters automatically

You can use the preliminary test to easily get all those parameters.

Getting the parameters manually

To configure CustomFields to support a module, you need at least 5 parameters:

  • the module's table_name (name of the sql table where the module stores its datas)
  • the module's context (name given by the module to the hookmanager)
  • the module's idvar (name of the variable that holds the sql row id of the record)
  • the module's trigger action(s) (triggers on creation and update, see the related chapter above)
  • the module's rights (for security purposes)

To get these parameters manually, you can take a look inside the code of the module's files you want to implement:

  • module's table name: either find it by yourself in the database (looks like llx_themodulename) or by printing it in actions_customfields.class.php by adding
    print_r($parameters)
    and search for $parameters->table_element
  • module's context: search for "callHooks(" without the quotes or take a look at the wiki: http://wiki.dolibarr.org/index.php/Hooks_system

or you can get it by printing it in the /htdocs/customfields/class/actions_customfields.class.php by adding print_r($parameters) and search for $parameters->context

  • module's trigger actions: search for "run_triggers(" or take a look at the wiki: http://wiki.dolibarr.org/index.php/Triggers#List_of_known_triggers_actions
  • module's idvar: look at the url when you access one of the module's record, and you should see something like fiche.php?xxxid=12 - you need the xxxid (eg: socid, facid, etc.).
  • module's security: in the page shown in URL when accessing the module's datasheet, edit this file and add
    print($user->rights)
    , then in the list try to find the correct right (generally something like $user->modulename->creer). You can also try to search for $user->rights in the sourcecode of this module.

With these values, edit the config file (/htdocs/customfields/conf/conf_customfields.lib.php), particularly the $modulesarray and $triggersarray variables:

Configure hooks and main parameters

Add every parameters except triggers in the $modulesarray:

$modulesarray = array(
                      array('context'=>'invoicecard', 'table_element'=>'facture', 'idvar'=>'facid', 'rights'=>array('$user->rights->facture->creer')), // Client Invoice
                      array('context'=>'membercard', 'table_element'=>'adherent', 'idvar'=>'rowid', 'rights'=>array('$user->rights->adherent->creer'), 'tabs'=>array('objecttype'=>'member_admin', 'function'=>'member_admin_prepare_head', 'lib'=>DOL_DOCUMENT_ROOT.'/core/lib/member.lib.php')), // Members / Adherents
                     ); // Edit me to add the support of another module - NOTE: Lowercase only!

There are also a lot of others parameters you can give to CustomFields so that the module gets better managed or to add a few other functionnalities, here is a non-exhaustive list:

  • context: name of the module's context.
  • table_element: name of the sql table that holds the object's datas.
  • idvar: name of the variable that holds the id of the object in the sql database (by default 'id' or 'rowid').
  • rights: prevent the custom fields to be editable unless all the rights set here are enabled (preventing custom fields to be viewed is useless because they won't show anyway if the page can't be loaded due to insufficient rights, so this is directly handled by the module).
  • tabs=>array(objecttype, function, lib, tabname, tabtitle): used to embed CustomFields's admin panel into another module's admin page.

Configure creation triggers

Add the triggers in $triggersarray: $triggersarray = array("order_create"=>"commande",

                      "yourmodule_triggeraction1"=>"yourmodule_tablename",
                      "yourmodule_triggeraction2"=>"yourmodule_tablename");

Note: generally you will be looking to implement the _CREATE.

Note2: if a triggeraction is not recognized by CustomFields (eg: mymodule_addpayment) then by default CustomFields will assimilate it to a _create action (it will create and save the values for the custom fields, just for a module's creation page).

Note3: CustomFields also supports a few other triggers like _CLONE, _UPDATE, _MODIFY, but they are not required and may not work depending on how your module works to manage these actions.

Configuration troubleshooting

  • If you cannot see the custom fields appear on your module's page, this means the hooks are not implemented or configured in CustomFields, please read the related chapters.
  • If the custom fields appear on the datasheet but there's no edit button icon: the module implements security but you did not configure it properly in CustomFields, please find the right security in $user->rights and set it in conf_customfields.lib.php.
  • If the custom fields appear on the creation page but are not saved: you forgot to set the trigger in $triggersarray, or the creation trigger does not exist for this module and you have to implement it. Jump to Module_CustomFields_Dev#Triggers.
  • If the custom fields appear on the datasheet but not on the creation page (or vice versa): A hook is missing on the page where the custom fields do not appear. Jump to Module_CustomFields_Dev#Hooks.
  • If the custom fields appear on the datasheet with the edit button, but when the edit button is clicked, everything disappear: either the $idvar is not properly set (look at the URL to know if you need to set idvar), or the hook is not properly made ($object is not defined and it's called by another name, but the hook use $object instead of the correct variable).
  • If the custom fields appear on the datasheet with the edit button, can be edited but cannot be saved (the data is not saved): same as the previous point: either $idvar or $object bug.

TO DO

To Do:

  • Add a confirmation when deleting a custom field (because user's datas are lost forever!).

Should do :

  • Checkbox (multiple checkboxes, one for each possible value) and multiselect (big select and can use SHIFT to multiselect) (store in XML file) (but impossible with constrained fields, can't store multiple foreign keys ids in one single sql field, but would work for other types like enum)

use fieldset, input checkbox and label for, eg:

   <fieldset><legend><label for="cf_productslist"> $langs->trans('cf_productslist')</label></legend>
   <input type="checkbox" name="cf_productslist_choice1" value="1" id="cf_productslist_choice1" /><label for="cf_productslist_choice1"> $langs->trans('cf_productslist_choice1')</label>
<input type="checkbox" name="cf_productslist_choice2" value="1" id="cf_productslist_choice2" /><label for="cf_productslist_choice2"> $langs->trans('cf_productslist_choice2')</label>
etc... </fieldset>
  • Refactor the trigger array: merge it with the modulesarray (along with a new way to specify which customfields trigger action a trigger should correspond, eg: 'linebill_insert'=>'customfields_create').
  • Add a javascript options values generator for the enum type (a hidden input that would be shown only when DropdownBox type is selected and that would permit to add options by clicking a plus button).

Known bugs :

  • in product and service modules, if you edit a field, the proposals and other fields below won't be shown, you need to refresh the page. This problem resides in Dolibarr I think (since we are simply using a hook).

Never/Maybe one day :

  • Add Upload field type (almost useless since we can attach files).
  • Add support for repeatable (predefined) invoices (the way it is currently managed makes it very difficult to manage this without making a big exception, adding specific functions in customfields modules that would not at all will be reusable anywhere else, when customfields has been designed to be as generic as possible to support any module and any version of dolibarr, because it's managed by a totally different table while it's still managed by the same module, CustomFields work with the paradigm: one module, one table).
  • AJAX? (but only optional, because else it won't work for mobile phones and tablets)
  • Extend meta-data storage using XML files?
  • Add an AJAX select box for constained values : when a constrained type is selected and a table is selected, a hidden select box would show up with the list of the fields of this table to choose the values that will be printed as the values for this customfield (eg: for table llx_users you could select the "nom" field and then it would automatically prepend "nom_" to the field's name).
  • A way to reorder the fields without deleting/recreating (add a third special column after fk_module, or in XML meta-data file?)