Line 7:
Line 7:
devdoc=This page|
devdoc=This page|
userdoc=[[Module_CustomFields]]|}}
userdoc=[[Module_CustomFields]]|}}
−
[[Category:FAQ EN]]
These notes are aimed to developpers who want to extend the functionnalities of the module or want to use it for extreme cases.
These notes are aimed to developpers who want to extend the functionnalities of the module or want to use it for extreme cases.
Line 125:
Line 124:
Eg:
Eg:
<source lang="php">
<source lang="php">
−
$extra = new stdClass(); // instanciating an empty object
+
$extra = array(); // $extra should always be an associative array, it's no longer an object
−
$extra->myproperty = 'Anything I want here'; // set a property/extra option like this
+
$extra['myproperty'] = 'Anything I want here'; // set a property/extra option like this
−
$extra->category->subcategory->prop = true; // we can set recursive properties, there's not any limit to own deep the hierarchy can be
+
$extra['category']['subcategory']['prop'] = true; // we can set recursive properties, there's not any limit to how deep the hierarchy can be
require_once(DOL_DOCUMENT_ROOT.'/customfields/class/customfields.class.php');
require_once(DOL_DOCUMENT_ROOT.'/customfields/class/customfields.class.php');
Line 135:
Line 134:
</source>
</source>
−
You can also use addCustomField() or updateCustomField() with an $extra object, and these functions will also call the setExtra() method.
+
You can also use addCustomField() or updateCustomField() with an $extra array, and these functions will also call the setExtra() method.
−
'''Note''': the extra options you set are '''appended''' to the one already stored in the database. However, you can overwrite them, just use the same name of property but with a different value.
+
'''Note''': the extra options you set are '''appended''' to the ones already stored in the database. However, you can also overwrite previous options, just use the same name of property but with a different value (the replacement is recursive, you can do it at any level in the hierarchy).
=== Fetching extra options ===
=== Fetching extra options ===
Line 165:
Line 164:
$fields = fetchAllFieldsStruct();
$fields = fetchAllFieldsStruct();
−
$fields->mycustomfield->extra->newproperty = 'This is a new property'; // add a new property over the old extra options sub-object
+
$fields->mycustomfield->extra['newproperty'] = 'This is a new property'; // add a new property over the old extra options sub-object
+
$fields->mycustomfield->extra['mycategory'] = 'Overwrite the old category'; // this will replace the previous value of 'mycategory' extra option
$customfields->setExtra('mycustomfield', $fields->mycustomfield->extra); // store the modified extra options back into the database for future usage
$customfields->setExtra('mycustomfield', $fields->mycustomfield->extra); // store the modified extra options back into the database for future usage
Line 219:
Line 219:
* fetchAllTables() fetch a list of all the tables in the database
* fetchAllTables() fetch a list of all the tables in the database
* fetchPrimaryField($table) fetch the column_name (and only the column_name!) of the primary field of a table. You can then use fetchFieldStruct() with the column_name to get any information you want.
* fetchPrimaryField($table) fetch the column_name (and only the column_name!) of the primary field of a table. You can then use fetchFieldStruct() with the column_name to get any information you want.
−
* fetchAny($columns, $table, $where='', $orderby='', $limitby='') allows you to issue any simple SQL query command, for example fetchAny('*', 'sometable') is equivalent to SELECT * FROM sometable; (but then everything is managed automatically and result is returned to you)
+
* fetchAny($columns, $table, $where='', $orderby='', $limitby='') allows you to issue any simple SQL query command, for example fetchAny('*', 'sometable') is equivalent to SELECT * FROM sometable; (but then everything is managed automatically and result is returned to you). NOTE: this method does NOT cache the database results, so that you always get the latest database state (whereas executeSQL() does cache queries to get faster responses).
* executeSQL($sql, $eventname) to execute any SQL query you want, this will return you a resource (and NOT a processed nuplet like fetchAny!). $eventname can be anything, it's just for Dolibarr logging facility.
* executeSQL($sql, $eventname) to execute any SQL query you want, this will return you a resource (and NOT a processed nuplet like fetchAny!). $eventname can be anything, it's just for Dolibarr logging facility.
* executeMultiSQL($sql, $eventname) to execute multiple SQL queries
* executeMultiSQL($sql, $eventname) to execute multiple SQL queries
Line 349:
Line 349:
Note: this method will always return an array of records object, for more consistency (and this is the main difference with fetch(null) ).
Note: this method will always return an array of records object, for more consistency (and this is the main difference with fetch(null) ).
+
+
=== Fetching referenced records of a constrained field ===
+
+
A constrained custom field points to another database table, and a value for this constrained field is the rowid of a record in the referenced table.
+
+
Thus, the principal purpose of a constrained custom field is not the value of the field in itself (it's simply an integer), but rather the other fields available from the referenced table, the constrained field being in fact just a pointer.
+
+
To fetch the remote record from the referenced table, use the fetchReferencedValuesList() of the CustomFields class:
+
+
<source lang="php">
+
$fkrecord = fetchReferencedValuesList($field, $id=null, $where=null, $allcolumns=false);
+
</source>
+
+
Where:
+
+
* $field is a custom field object (fetched using fetchFieldStruct() or fetchAllFieldsStruct()),
+
* $id is the rowid of the record to get, just like fetch() and fetchAll(). Can be null to fetch all remote records (or to use the $where clause).
+
* $where is a string without the WHERE clause (eg: "fk_facture=1") to get finer control. In this case, you can use $id or set $id=null depending on your purpose. This is mainly a private argument to allow for cascade option.
+
* $allcolumns if false, it will fetch only the required columns to do the smart value substitution. If true, it will fetch all remote columns (useful for ODT substitution). If an array of strings, each string will be a column to fetch from the remote database.
+
+
Note that this function is NOT recursive (it will fetch the records from the referenced table, and that's all, it won't fetch the custom fields of the referenced table nor recursively fetch the remote constrained custom fields). To do recursive fetching, see in the Facade API.
+
+
Note2: this function automatically manages Smart Value Substitution depending of the $field->column_name.
+
+
=== Recursive fetching referenced records of a constrained field ===
+
+
If you want to recursively fetch a constrained field (eg: constrainedfield1->ref_table->constrainedfield2->ref_table2->...), you need another function called fetchReferencedValuesRec(). Technically, this function is in the Facade API (inside /customfields/lib/customfields_aux.lib.php), but it isn't meant to be used by lambda users (contrary to customfields_fill_object()), but is mainly a helper function for the rest of the facade API.
+
+
fetchReferencedValuesRec() works very similarly to fetchReferencedValuesList(), but it will work recursively.
+
+
<source lang="php">
+
$fkrecord = fetchReferencedValuesRec($customfields, $field, $id, $recursive=true)
+
</source>
+
+
Where $customfields is the CustomFields instance for the module you are working on, $field is the constrained field you want to fetch, $id is the record's id you want to fetch, and $recursive enables or disables the recursion.
+
+
Note that this function automatically manages Smart Value Substitution (and also recursively).
+
+
Note2: This function tries to automatically avoid an infinite recursion loop by using a $blacklist: for a given constrained custom field, a table won't be visited twice. This means that for example you can do: invoice->user->socpeople->invoice, this will work and every referenced fields will be fetched, but it will automatically stop the recursion at the second invoice, because else it could continue in a loop to user, socpeople, invoice, user, socpeople, etc.
+
+
Note3: when adding recursively referenced fields, the key in the returned array will be prefixed by the name of the custom field that led to those remote fields. Thus, you can have something like "cf_myfield1_cf_remotefield1_cf_remotefield2" if you have a constrained field cf_myfield1->cf_remotefield1->cf_remotefield2.
== Input forms for custom fields ==
== Input forms for custom fields ==
Line 365:
Line 406:
<source lang="php">
<source lang="php">
−
print $customfields->showInputField($field, $currentvalue=null, $moreparam='');
+
print $customfields->showInputField($field, $currentvalue=null, $moreparam='', $ajax_php_callback='');
</source>
</source>
−
showInputField() takes 3 parameters:
+
showInputField() takes 4 parameters:
* $field is a CustomFields structure object (eg: returned by fetchFieldStruct()) and is necessary to detect the right datatype and the possible values.
* $field is a CustomFields structure object (eg: returned by fetchFieldStruct()) and is necessary to detect the right datatype and the possible values.
* $currentvalue is the current value if you want to preselect a value (for example if the user already defined a value, you can reload it here). This is optional, in case it is not defined, either a null/empty value will be chosen, or either the default value defined when creating the custom field.
* $currentvalue is the current value if you want to preselect a value (for example if the user already defined a value, you can reload it here). This is optional, in case it is not defined, either a null/empty value will be chosen, or either the default value defined when creating the custom field.
* $moreparam is used to add more HTML attributes, if you want for example to add some css styling or whatever you want.
* $moreparam is used to add more HTML attributes, if you want for example to add some css styling or whatever you want.
+
* $ajax_php_callback is optional and allows to specify the relative url to the php callback script. If specified, an AJAX script will be automatically attached to this field (see AJAX-PHP callback below).
showInputField() only prints ONE HTML input field, thus you have to enclose it/them by your own HTML form.
showInputField() only prints ONE HTML input field, thus you have to enclose it/them by your own HTML form.
Line 404:
Line 446:
print $customfields->showInputForm($id, $field, $currentvalue=null, $idvar='id', $page=null, $moreparam='');
print $customfields->showInputForm($id, $field, $currentvalue=null, $idvar='id', $page=null, $moreparam='');
</source>
</source>
+
+
=== AJAX-PHP callback ===
+
+
'''showInputFieldAjax()''' will output an AJAX script (using jQuery) to send and receive data to and from a specified php script. The AJAX script will automatically manage HTML form input's updating as long as the received data follows some convention.
+
+
An AJAX script can be attached to any field (including non-CustomFields) by using the method:
+
+
<source lang="php">
+
print $customfields->showInputFieldAjax($id, $php_callback_url, $on_func="change", $request_type="post");
+
</source>
+
+
Where:
+
* $id is the HTML id of the field you want to attach the AJAX to.
+
* $phpcallback the relative path from Dolibarr's htdocs root folder to the php script that will receive and send back data.
+
* $on_func is the Javascript or jQuery event that should trigger the AJAX script (by default on change).
+
* $request_type is the HTML query type that will be used to send the data to the PHP callback (either "get" or "post").
+
+
The AJAX script will automatically manage sending/receiving of data:
+
+
* automatically send all form's inputs values, as well as the current calling field's name and value. The sending happens on change by default, but another event can be set using the $on_event argument. Data will be sent using standard GET or POST.
+
* Upon reception of data from the php script, the AJAX script will automatically parse the data and update the HTML fields.
+
+
The PHP callback script must send back to AJAX the data in JSON encoded format, and with with a specific format:
+
+
* the data must be an associative array, where each entry's key is the name (not ID!) of an HTML field to update.
+
* each entry's value is an associative array, where each sub-entry's key is the action to do. Available actions: 'options', 'value', 'html', 'alert'.
+
* each sub-entry's value is the value for this action and HTML field.
+
+
Thus, this approach allows to generically support a wide range of updating actions, managed automatically.
+
+
You, the implementer, have just to edit the PHP callback script and return an associative array defining what action you want for each field you want to update. This is exactly what has been implemented in the Custom AJAX Functions library, which simplifies even more your job by managing automatically the step described here (generating AJAX and formatting data), so that you can focus on '''making the data'''.
== Printing custom fields in documents ==
== Printing custom fields in documents ==
Line 430:
Line 503:
Another method '''printFieldPDF()''' can be used to properly print a value for a PDF (properly encoding the characters).
Another method '''printFieldPDF()''' can be used to properly print a value for a PDF (properly encoding the characters).
+
+
Powertip: this function can also be used to get the Smart Value Substituted value of the field (instead of printing the rowid) in the field is constrained.
=== Simple printing in documents ===
=== Simple printing in documents ===
Line 447:
Line 522:
Another method '''simpleprintFieldPDF()''' can be used to properly print a value for a PDF (properly encoding the characters).
Another method '''simpleprintFieldPDF()''' can be used to properly print a value for a PDF (properly encoding the characters).
+
+
Powertip: this function can also be used to get the Smart Value Substituted value of the field (instead of printing the rowid) in the field is constrained.
=== Find the label ===
=== Find the label ===
Line 817:
Line 894:
<source lang="php">
<source lang="php">
−
$parameters=array('line'=>$line,'fk_parent_line'=>$line->fk_parent_line);
+
$parameters=array('line'=>$line);
−
echo $hookmanager->executeHooks('formEditProductOptions',$parameters,$this,$action);
+
echo $hookmanager->executeHooks('formEditProductOptions',$parameters,$object,$action);
</source>
</source>
−
Hooks names:
+
The two important things here are that you supply:
−
* formEditProductOptions
+
* $line: the line object, with a $line->rowid property.
−
* formCreateProductOptions ($line is not required here, because we create a new product line! you can leave $parameters=array(); empty).
+
* $object: the parent object of the line, with a $object->rowid property. For example, if $line is an invoice line, then $object must be the parent invoice object. This is necessary so that CustomFields can automatically recognize the link between the twos.
+
+
Possible hooks names:
+
* formEditProductOptions: called when you edit a line.
+
* formCreateProductOptions: called when you create a line. $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.
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.
Line 947:
Line 1,028:
== To Do ==
== To Do ==
−
Nothing here!
+
+
* Better management of hidden fields with custom cascading (eg: works great on creation form, but on edit form and creation form with edit action, if the parents already have a value, the hidden fields will stay hidden! This is because of the AJAX is not called, and this is normal). -> Propose a better custom cascade function storing the hidden field's state inside extraoptions?
== To Document ==
== To Document ==
Line 967:
Line 1,049:
* In ODTs, create special tags to directly access day, month or year value of a date type field independently of the rest.
* In ODTs, create special tags to directly access day, month or year value of a date type field independently of the rest.
* New overloading function's action: "get", similar to view except that it works not only on Dolibarr's view but whenever someone tries to access this field's value.
* New overloading function's action: "get", similar to view except that it works not only on Dolibarr's view but whenever someone tries to access this field's value.
−
* Implementation of custom fields in CSV exports (may need a few new hooks in Dolibarr?).
* Add Upload and Image field types (this would allow for an easier integration inside ODT and PDF templates than the current workflow of Dolibarr).
* Add Upload and Image field types (this would allow for an easier integration inside ODT and PDF templates than the current workflow of Dolibarr).
Line 976:
Line 1,057:
== Never/Maybe one day ==
== Never/Maybe one day ==
* 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).
* 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).
−
* Add an AJAX select box for constrained 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).
+
* Add an AJAX select box for constrained values to automatically select the appropriate name for Smart Value Substitution : 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).
* Fine-grained rights management: rights per group, per user, and per field.
* Fine-grained rights management: rights per group, per user, and per field.
* Replace overloading functions (extend class) by hookmanager if possible (but how to simulate the *full switch? Plus this will force users to make their own modules to make those functions, and to disable/renable their modules everytime they will add a new function...).
* Replace overloading functions (extend class) by hookmanager if possible (but how to simulate the *full switch? Plus this will force users to make their own modules to make those functions, and to disable/renable their modules everytime they will add a new function...).
* 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'). But the triggers aren't necessarily associated with a specific module...
* 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'). But the triggers aren't necessarily associated with a specific module...
+
* AJAX autocompletion / auto population of field's content when typing the beginning of a value in a text box.