Module CustomFields Cases
This page lists a compilation of concrete, practical examples uses of CustomFields and of its various features.
If you had an experience with CustomFields, you can also add here your case as an example for others to get inspired.
How to make a generic PDF template listing all custom fields
To make a generic PDF template listing all custom fields, you just need to take an existing PDF template, then you need to follow these 3 steps:
1/ Edit the class name (required, else it will conflict with the original template).
Eg: if we take the crabe template for invoices, we change:
class pdf_crabe extends ModelePDFFactures {
// ... php code ...
function __constructor($db) {
// ... php code ...
$this->name = "crabe";
into:
class pdf_customfields extends ModelePDFFactures {
// ... php code ...
function __constructor($db) {
// ... php code ...
$this->name = "customfields";
2/ Copy this generic function at the end of the PDF:
/**
* \brief Show the customfields in a new page
* \param pdf PDF factory
* \param object Object invoice/propale/order/etc... (CustomFields simpler functions will automatically adapt)
* \param outputlangs Object lang for output
*/
function _pagecustomfields(&$pdf,$object,$outputlangs)
{
global $conf;
$default_font_size = pdf_getPDFFontSize($outputlangs); // set default PDF font size
// Init and main vars
include_once(DOL_DOCUMENT_ROOT.'/customfields/lib/customfields_aux.lib.php');
// Filling the $object with customfields (you can then access customfields by doing $object->customfields->cf_yourfield)
$customfields = customfields_fill_object($object, null, $outputlangs, null, true);
// Setting the starting position of the text cursor
$pdf->SetXY($this->page_largeur - $this->marge_droite - ($pdf->GetStringWidth($titre) + 3), $pdf->GetY()+4);
$pdf->SetY($pdf->GetY()+1);
// Printing the customfields
foreach ($object->customfields as $label=>$value) { // $value is already formatted!
// Get translated label
$translatedlabel = $customfields->findLabelPDF($label, $outputlangs); // translated label of the customfield (not translated by default in customfields_fill_object() because a field should always be accessible by a base name, whatever the translation is)
// PDF formatting, placement and printing
$pdf->SetFont('','B', $default_font_size);
$pdf->MultiCell(0,3, $translatedlabel.' ($object->customfields->'.$label.'): '.$value, 0, 'L'); // printing the customfield
$pdf->SetY($pdf->GetY()+1); // line return for the next printing
}
return 1;
}
Alternative function (with a bit less functionalities, but more controllable):
/**
* \brief Show the customfields in a new page (used to debug if CustomFields setup is correct)
* \param pdf PDF factory
* \param object Object invoice/propal/product/whatever...
* \param outputlangs Object lang for output
*/
function _pagecustomfields(&$pdf,$object,$outputlangs)
{
$default_font_size = pdf_getPDFFontSize($outputlangs);
if (empty($object->table_element) or empty($object->id)) {
$pdf->MultiCell(0,3, "Current \$object is not compatible with CustomFields, could not find table_element or id.", 0, 'L');
return 1;
}
// Init and main vars
include_once(DOL_DOCUMENT_ROOT.'/customfields/class/customfields.class.php');
$customfields = new CustomFields($this->db, $object->table_element);
// Fetching custom fields records
$fields = $customfields->fetch($object->id);
if (!isset($fields)) {
$pdf->MultiCell(0,3, "No custom field could be found for this object. Please check your configuration (did you create at least one customfield and set a value in the current datasheet?)", 0, 'L');
return 1;
} else {
// Setting the starting position of the text cursor
$pdf->SetXY($this->page_largeur - $this->marge_droite - ($pdf->GetStringWidth($titre) + 3), $pdf->GetY()+4);
$pdf->SetY($pdf->GetY()+1);
// Printing the customfields
foreach ($fields as $key=>$field) {
$translatedname = $customfields->findLabelPDF($key, $outputlangs); // label of the customfield
$value = $customfields->simpleprintFieldPDF($key, $field, $outputlangs); // value (cleaned and properly formatted) of the customfield
$pdf->SetFont('','B', $default_font_size);
$pdf->MultiCell(0,3, $translatedname.': '.$value, 0, 'L'); // printing the customfield
$pdf->SetY($pdf->GetY()+1); // line return for the next printing
}
}
return 1;
}
3/ Call the _pagecustomfields function at the right place (generally, after _pagefoot and just before $pdf->Close()):
// Pied de page
$this->_pagefoot($pdf,$object,$outputlangs);
$pdf->AliasNbPages();
// CustomFields
if ($conf->global->MAIN_MODULE_CUSTOMFIELDS) { // if the customfields module is activated...
// Add a page with the customfields
$pdf->AddPage();
$this->_pagecustomfields($pdf,$object,$outputlangs);
$pagenb++;
}
$pdf->Close(); // you should first search for this, you should only find one occurrence
Then go to the module's admin panel (eg: invoice admin panel) and enable the template you've just created.
You can now use it to test if your customfields work.
Conditions on fields selection
Sometimes you may want to show a DropdownBox or Constraint (or another multi-choice type), but you may want to show or hide choices based on conditions (eg: only if it's a prospect, only if this zone was selected, only if this category was selected, etc..).
To know which is the best solution you should choose to implement your condition, you should first know in which category your condition falls in.
Conditions categories
Conditions can be categorized into 4 types:
- static condition: choices are restrained on a condition that never changes (eg: only show third-parties that are suppliers, or lowercase all data on sql insertion, or check that a field is above a certain number, etc.).
- semi-dynamic condition: choices that are constrained on a static table but based on the value of another field/customfield.
- dynamic: everything is dynamic: choices are based on a subfield of a field of the current module, so that there's no material table and it's impossible to create a view (because you would need to create one view per id) (eg: show only the contacts attached to the third-party being accessed)
- timed condition: a condition that is based on time or recurrence (eg: at 5:00 AM delete old records, etc..)
Conditions solutions
Now that you know what kind of condition you want to set, you can choose one of the following solutions depending on your preferences and knowledge.
In any case, you can always use the CustomFields's overload functions to fit your needs for nearly every type of condition.
Static condition
- View: create a view on the table based on the condition, and then create a custom field constrained on this view (which is just like any table), eg: only show third-parties that are suppliers:
CREATE VIEW llx_societe_supplier_view AS
SELECT *
FROM llx_societe
WHERE fournisseur=1;
WARNING: foreign keys (and thus constraints) can't be used on views with MySQL, and materialized views neither exist, so you can't use this method with MySQL.
- Check
- Trigger
- SQL Transformations
- CustomFields's overload functions
Semi-dynamic condition
- View
- Check
- Trigger
- CustomFields's overload functions
Dynamic condition
- CustomFields's overload functions
- Making your own module, which calls the CustomFields class
Timed condition
- SQL scheduled events
- Cron job
- CustomFields's overload functions
Overloading functions
The goal is to show two custom fields on the Third-Party module: one which gives the zone (secteur) of the third-party (Isere, Alpes du sud, Haute-Savoie...) and the other which gives relative to the zone the list of all the ski resorts (station_a) inside the selected zone.
We have one table llx_k_station (rowid, station (char50), secteur(char50)) which contains the following:
1,les 2 alpes,isère 2,chamrousse,isère 3,alpe d'huez,isère 4,vars,alpes du sud 5,risoul,alpes du sud etc...
In CustomFields's admin panel, we create two custom fields: 1-secteur: returns the list of all zones - type DropdownBox: enum('- Aucun','Alpes du Sud','export','Haute-Savoie','Isère', etc...) 2-station_a: returns the list of all ski resorts - type constraint on llx_k_station
Here is the code to put in customfields_fields_extend.lib.php that will allow to show only the ski resorts corresponding to the selected zone:
function customfields_field_editview_societe_station_a (&$currentmodule, &$object, &$parameters, &$action, &$user, &$idvar, &$rightok, &$customfields, &$field, &$name, &$value) {
global $db; // allow to access database functions
$sql="SELECT llx_k_station.station, llx_k_station.rowid FROM llx_societe_customfields INNER JOIN llx_k_station ON llx_societe_customfields.Secteur = llx_k_station.secteur WHERE llx_societe_customfields.fk_societe=".$object->id;
$result=$db->query($sql);
if ($result) {
$num = $db->num_rows($result);
$i = 0;
$myarray = array();
while ($i < $num)
{
$obj = $db->fetch_object($result);
$myarray []=array('id'=>$obj->rowid, 'value'=>$obj->station);
$i++;
}
$value = $myarray;
$db->free($result);
}
}
Thank's to manub for giving this case.
Linking Dolibarr objects from two different modules
Dolibarr sometimes offers the possibility to link two different modules. Eg: when you convert an Intervention card into an Invoice.
In this case, you might want in your templates to fetch the custom fields of the linked object. Eg: you generate a PDF from the Invoice, but you want to get the custom fields from the Intervention that was converted into the Invoice.
The key here is to use the llx_element_element: this is a standard Dolibarr table that stores all links between objects of two different modules.
Here is the structure:
rowid | fk_source | sourcetype | fk_target | targettype ------------------------------------------------------- 1 | 2 | fichinter | 5 | facture
You can then use a php code like the following to populate your $object with customfields:
// Init and main vars for CustomFields
dol_include_once('/customfields/lib/customfields_aux.lib.php');
// Filling the $object with customfields (you can then access customfields by doing $object->customfields->cf_yourfield)
// This will also create and returns us an instance of the CustomFields class, which we will use to manually query the llx_element_element table
$customfields = customfields_fill_object($object, null, $outputlangs, 'facture', true); // beautified values
// Querying the llx_element_element table manually to get the link between the invoice and the linked intervention card (we want the id of the intervention card)
// fetchAny($columns, $table, $where='', $orderby='', $limitby='')
$eltrow = $customfields->fetchAny('*', MAIN_DB_PREFIX.'element_element', 'targettype="'.$object->element.'" and fk_target='.$object->id.' and sourcetype="fichinter"');
// Save the intervention's id
$sourceid = $eltrow[0]['fk_source'];
// Crafting a dummy intervention object
$fromobject = object(); // Init the object
$fromobject->id = $sourceid; // We need the id and..
$fromobject->table_element = 'fichinter'; // the table_element (name of the table)
// We can then fill $object with the intervention custom fields (we store them in a sub property 'fichinter' to avoid conflicts with invoice's custom fields)
customfields_fill_object($object, $fromobject, $outputlangs, 'fichinter', true);
// Now you can access invoices custom fields using:
print($object->customfields->facture->cf_myfield);
// And access intervention custom fields with:
print($object->customfields->fichinter->cf_myfield);
Thank's to netassopro for the tip.
Modify the price of every product/service line with a coefficient
Here we will go through the creation and management of a custom field called coefficient that will change the total price by modifying it by the coefficient.
Creation of the custom field called coefficient
First you have to create a custom field. We will create one for Client Invoice Lines.
Go to the CustomFields administration panel, then into the Client Invoice Lines tab, then click on the button New Field, name the field coefficient, and set the type to Double.
When you go to client invoices in Dolibarr, you should then see something similar to this:
You can see that our coefficient field was successfully created.
Implementing the change of price in ODT
There's no eval implementation currently inside ODT templates, but there is a kind of hook implementation, called substitution functions.
You can either create your own module, then create your own substitution function, but for the sake of simplicity in this tutorial, we will directly use the CustomFields substitution function. However, please remember that you will have to redo your changes everytime you will update the CustomFields module, so if you don't want to do that it would be better if you create your own module and substitution function.
Open the file htdocs/customfields/core/substitutions/functions_customfields.lib.php and go to the function customfields_completesubstitutionarray_lines.
At the very end of this function, but inside the customfields check:
if ($conf->global->MAIN_MODULE_CUSTOMFIELDS) {
But just before the ending bracket, you should add the following code:
if ($object->table_element_line === 'facturedet') { // First check that we are in the right module, we don't want to use coefficient for other modules if we didn't define the custom field there
$substitutionarray['line_price_full'] = $substitutionarray['line_price_ttc'] * $substitutionarray['cf_coefficient']; // multiply the total price with our coefficient
}
This code will add a new tag {line_price_full} that you can then use inside your ODT templates.
Just place this tag in a table in your ODT document, between the table tags ( [!-- BEGIN row.lines --] ... [!-- END row.lines --] ), and this will print your full price.
Implementing the change of price in PDF
Implementing the change of price in the Dolibarr interface
Unluckily there is not (yet) any hooking context to modify how lines of products/services are printed, thus the only thing you can do is directly edit the htdocs/core/tpl/objectline_view.tpl.php template file, then load your customfield manually and print it the way you want using HTML formatting, for example: