diff --git a/htdocs/core/ajax/selectobject.php b/htdocs/core/ajax/selectobject.php
index 14372a405b4..edc5eb32998 100644
--- a/htdocs/core/ajax/selectobject.php
+++ b/htdocs/core/ajax/selectobject.php
@@ -39,34 +39,92 @@ if (!defined('NOREQUIRESOC')) {
// Load Dolibarr environment
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
-$objectdesc = GETPOST('objectdesc', 'alpha');
+$extrafields = new ExtraFields($db);
+
+$objectdesc = GETPOST('objectdesc', 'alphanohtml', 0, null, null, 1);
$htmlname = GETPOST('htmlname', 'aZ09');
$outjson = (GETPOST('outjson', 'int') ? GETPOST('outjson', 'int') : 0);
-$id = GETPOST('id', 'int');
-$filter = GETPOST('filter', 'alphanohtml'); // Universal Syntax filter
+$id = GETPOSTINT('id');
+$objectfield = GETPOST('objectfield', 'alpha'); // 'MyObject:field' or 'MyModule_MyObject:field' or 'MyObject:option_field' or 'MyModule_MyObject:option_field'
if (empty($htmlname)) {
httponly_accessforbidden('Bad value for param htmlname');
}
-$InfoFieldList = explode(":", $objectdesc);
-$classname = $InfoFieldList[0];
-$classpath = $InfoFieldList[1];
-if (!empty($classpath)) {
- dol_include_once($classpath);
- if ($classname && class_exists($classname)) {
- $objecttmp = new $classname($db);
+if (!empty($objectfield)) {
+ // Recommended method to call selectobject.
+ // $objectfield is Object:Field that contains the definition (in table $fields or extrafield). Example: 'Societe:t.ddd' or 'Societe:options_xxx'
+
+ $tmparray = explode(':', $objectfield);
+ $objectdesc = '';
+
+ // Load object according to $id and $element
+ $objectforfieldstmp = fetchObjectByElement(0, strtolower($tmparray[0]));
+
+ $reg = array();
+ if (preg_match('/^options_(.*)$/', $tmparray[1], $reg)) {
+ // For a property in extrafields
+ $key = $reg[1];
+ // fetch optionals attributes and labels
+ $extrafields->fetch_name_optionals_label($objectforfieldstmp->table_element);
+
+ if (!empty($extrafields->attributes[$objectforfieldstmp->table_element]['type'][$key]) && $extrafields->attributes[$objectforfieldstmp->table_element]['type'][$key] == 'link') {
+ if (!empty($extrafields->attributes[$objectforfieldstmp->table_element]['param'][$key]['options'])) {
+ $tmpextrafields = array_keys($extrafields->attributes[$objectforfieldstmp->table_element]['param'][$key]['options']);
+ $objectdesc = $tmpextrafields[0];
+ }
+ }
+ } else {
+ // For a property in ->fields
+ $objectdesc = $objectforfieldstmp->fields[$tmparray[1]]['type'];
+ $objectdesc = preg_replace('/^integer[^:]*:/', '', $objectdesc);
}
}
-if (!is_object($objecttmp)) {
- httponly_accessforbidden('Bad value for param objectdesc');
+
+if ($objectdesc) {
+ // Example of value for $objectdesc:
+ // Bom:bom/class/bom.class.php:0:t.status=1
+ // Bom:bom/class/bom.class.php:0:t.status=1:ref
+ // Bom:bom/class/bom.class.php:0:(t.status:=:1) OR (t.field2:=:2):ref
+ $InfoFieldList = explode(":", $objectdesc, 4);
+ $vartmp = (empty($InfoFieldList[3]) ? '' : $InfoFieldList[3]);
+ $reg = array();
+ if (preg_match('/^.*:(\w*)$/', $vartmp, $reg)) {
+ $InfoFieldList[4] = $reg[1]; // take the sort field
+ }
+ $InfoFieldList[3] = preg_replace('/:\w*$/', '', $vartmp); // take the filter field
+
+ $classname = $InfoFieldList[0];
+ $classpath = $InfoFieldList[1];
+ //$addcreatebuttonornot = empty($InfoFieldList[2]) ? 0 : $InfoFieldList[2];
+ $filter = empty($InfoFieldList[3]) ? '' : $InfoFieldList[3];
+ $sortfield = empty($InfoFieldList[4]) ? '' : $InfoFieldList[4];
+
+ // Load object according to $id and $element
+ $objecttmp = fetchObjectByElement(0, strtolower($InfoFieldList[0]));
+
+ // Fallback to another solution to get $objecttmp
+ if (empty($objecttmp) && !empty($classpath)) {
+ dol_include_once($classpath);
+
+ if ($classname && class_exists($classname)) {
+ $objecttmp = new $classname($db);
+ }
+ }
}
-/*
-// Load object according to $id and $element
-$object = fetchObjectByElement($id, $element);
+// Make some replacement
+$sharedentities = getEntity(strtolower($objecttmp->element));
+$filter = str_replace(
+ array('__ENTITY__', '__SHARED_ENTITIES__', '__USER_ID__', '$ID$'),
+ array($conf->entity, $sharedentities, $user->id, $id),
+ $filter
+);
+
+/*
$module = $object->module;
$element = $object->element;
$usesublevelpermission = ($module != $element ? $element : '');
@@ -86,15 +144,13 @@ restrictedArea($user, $objecttmp->element, $id);
* View
*/
-//print ''."\n";
-//print_r($_GET);
-
-//$langs->load("companies");
-
$form = new Form($db);
top_httphead($outjson ? 'application/json' : 'text/html');
+//print ''."\n";
+//print_r($_GET);
+
$arrayresult = $form->selectForFormsList($objecttmp, $htmlname, '', 0, $searchkey, '', '', '', 0, 1, 0, '', $filter);
$db->close();
diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php
index 4ab1a0577ff..140b7a92d26 100644
--- a/htdocs/core/class/commonobject.class.php
+++ b/htdocs/core/class/commonobject.class.php
@@ -7711,7 +7711,8 @@ abstract class CommonObject
}
}
- $out = $form->selectForForms($param_list[0], $keyprefix.$key.$keysuffix, $value, $showempty, '', '', $morecss, $moreparam, 0, empty($val['disabled']) ? 0 : 1);
+ //$out = $form->selectForForms($param_list[0], $keyprefix.$key.$keysuffix, $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '');
+ $out = $form->selectForForms($param_list_array[0], $keyprefix.$key.$keysuffix, $value, $showempty, '', '', $morecss, $moreparam, 0, (empty($val['disabled']) ? 0 : 1), '', $this->element.':'.$key.$keysuffix);
if (!empty($param_list_array[2])) { // If the entry into $fields is set, we must add a create button
if ((!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) // // To avoid to open several times the 'Plus' button (we accept only one level)
diff --git a/htdocs/core/class/extrafields.class.php b/htdocs/core/class/extrafields.class.php
index ec4f72027be..92483d49a6b 100644
--- a/htdocs/core/class/extrafields.class.php
+++ b/htdocs/core/class/extrafields.class.php
@@ -933,7 +933,7 @@ class ExtraFields
* @param string $keyprefix Suffix string to add before name and id of field (can be used to avoid duplicate names)
* @param string $morecss More css (to defined size of field. Old behaviour: may also be a numeric)
* @param int $objectid Current object id
- * @param string $extrafieldsobjectkey The key to use to store retreived data (for example $object->table_element)
+ * @param string $extrafieldsobjectkey The key to use to store retreived data (commonly $object->table_element)
* @param string $mode 1=Used for search filters
* @return string
*/
@@ -1462,6 +1462,7 @@ class ExtraFields
}
// Si l'on a un AND ou un OR, avant ou après
+ $matchCondition = array();
preg_match('#(AND|OR|) *('.$word.') *(AND|OR|)#', $InfoFieldList[4], $matchCondition);
while (!empty($matchCondition[0])) {
// If the two sides differ but are not empty
@@ -1581,11 +1582,23 @@ class ExtraFields
}
} elseif ($type == 'link') {
$param_list = array_keys($param['options']); // $param_list='ObjectName:classPath'
+ /* Removed.
+ The selectForForms is called with parameter $objectfield defined, so tha app can retreive the filter inside the ajax component instead of being provided as parameters. The
+ filter was use to pass SQL requests leading to serious SQL injection problem. This should not be possible. Also the call of the ajax was broken by some WAF.
if (strpos($param_list[0], '$ID$') !== false && !empty($objectid)) {
$param_list[0] = str_replace('$ID$', $objectid, $param_list[0]);
- }
+ }*/
$showempty = (($required && $default != '') ? 0 : 1);
- $out = $form->selectForForms($param_list[0], $keyprefix.$key.$keysuffix, $value, $showempty, '', '', $morecss);
+
+ $tmparray = explode(':', $param_list[0]);
+
+ $element = $extrafieldsobjectkey; // $extrafieldsobjectkey comes from $object->table_element but we need $object->element
+ if ($element == 'socpeople') {
+ $element = 'contact';
+ }
+
+ //$out = $form->selectForForms($param_list[0], $keyprefix.$key.$keysuffix, $value, $showempty, '', '', $morecss, '', 0, 0, '');
+ $out = $form->selectForForms($tmparray[0], $keyprefix.$key.$keysuffix, $value, $showempty, '', '', $morecss, '', 0, 0, '', $element.':options_'.$key);
} elseif ($type == 'password') {
// If prefix is 'search_', field is used as a filter, we use a common text field.
$out = ''; // Hidden field to reduce impact of evil Google Chrome autopopulate bug.
diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php
index a714c6c6c0f..7f6a397e0c6 100644
--- a/htdocs/core/class/html.form.class.php
+++ b/htdocs/core/class/html.form.class.php
@@ -8000,62 +8000,100 @@ class Form
* Can use autocomplete with ajax after x key pressed or a full combo, depending on setup.
* This is the generic method that will replace all specific existing methods.
*
- * @param string $objectdesc ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]
- * @param string $htmlname Name of HTML select component
- * @param int $preselectedvalue Preselected value (ID of element)
- * @param string $showempty ''=empty values not allowed, 'string'=value show if we allow empty values (for example 'All', ...)
- * @param string $searchkey Search criteria
- * @param string $placeholder Place holder
- * @param string $morecss More CSS
- * @param string $moreparams More params provided to ajax call
- * @param int $forcecombo Force to load all values and output a standard combobox (with no beautification)
- * @param int $disabled 1=Html component is disabled
- * @param string $selected_input_value Value of preselected input text (for use with ajax)
- * @return string Return HTML string
+ * @param string $objectdesc 'ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]'. For hard coded custom needs. Try to prefer method using $objectfield instead of $objectdesc.
+ * @param string $htmlname Name of HTML select component
+ * @param int $preselectedvalue Preselected value (ID of element)
+ * @param string $showempty ''=empty values not allowed, 'string'=value show if we allow empty values (for example 'All', ...)
+ * @param string $searchkey Search criteria
+ * @param string $placeholder Place holder
+ * @param string $morecss More CSS
+ * @param string $moreparams More params provided to ajax call
+ * @param int $forcecombo Force to load all values and output a standard combobox (with no beautification)
+ * @param int $disabled 1=Html component is disabled
+ * @param string $selected_input_value Value of preselected input text (for use with ajax)
+ * @param string $objectfield Object:Field that contains the definition (in table $fields or extrafields). Example: 'Object:xxx' or 'Module_Object:xxx' or 'Object:options_xxx' or 'Module_Object:options_xxx'
+ * @return string Return HTML string
* @see selectForFormsList(), select_thirdparty_list()
*/
- public function selectForForms($objectdesc, $htmlname, $preselectedvalue, $showempty = '', $searchkey = '', $placeholder = '', $morecss = '', $moreparams = '', $forcecombo = 0, $disabled = 0, $selected_input_value = '')
+ public function selectForForms($objectdesc, $htmlname, $preselectedvalue, $showempty = '', $searchkey = '', $placeholder = '', $morecss = '', $moreparams = '', $forcecombo = 0, $disabled = 0, $selected_input_value = '', $objectfield = '')
{
- global $conf, $user;
+ global $conf, $extrafields;
+ $objectdescorig = $objectdesc;
$objecttmp = null;
+ $InfoFieldList = array();
- // Example of value for $objectdec:
- // Bom:bom/class/bom.class.php:0:t.status=1
- // Bom:bom/class/bom.class.php:0:t.status=1:ref
- // Bom:bom/class/bom.class.php:0:(t.status:=:1):ref
- $InfoFieldList = explode(":", $objectdesc, 4);
- $vartmp = (empty($InfoFieldList[3]) ? '' : $InfoFieldList[3]);
- $reg = array();
- if (preg_match('/^.*:(\w*)$/', $vartmp, $reg)) {
- $InfoFieldList[4] = $reg[1]; // take the sort field
- }
- $InfoFieldList[3] = preg_replace('/:\w*$/', '', $vartmp); // take the filter field
+ if ($objectfield) { // We must retreive the objectdesc from the field or extrafield
+ $tmparray = explode(':', $objectfield);
+ $objectdesc = '';
- $classname = $InfoFieldList[0];
- $classpath = $InfoFieldList[1];
- $addcreatebuttonornot = empty($InfoFieldList[2]) ? 0 : $InfoFieldList[2];
- $filter = empty($InfoFieldList[3]) ? '' : $InfoFieldList[3];
- $sortfield = empty($InfoFieldList[4]) ? '' : $InfoFieldList[4];
+ // Load object according to $id and $element
+ $objectforfieldstmp = fetchObjectByElement(0, strtolower($tmparray[0]));
- if (!empty($classpath)) {
- dol_include_once($classpath);
+ $reg = array();
+ if (preg_match('/^options_(.*)$/', $tmparray[1], $reg)) {
+ // For a property in extrafields
+ $key = $reg[1];
+ // fetch optionals attributes and labels
+ $extrafields->fetch_name_optionals_label($objectforfieldstmp->table_element);
- if ($classname && class_exists($classname)) {
- $objecttmp = new $classname($this->db);
-
- // Make some replacement
- $sharedentities = getEntity(strtolower($classname));
- $filter = str_replace(
- array('__ENTITY__', '__SHARED_ENTITIES__', '__USER_ID__'),
- array($conf->entity, $sharedentities, $user->id),
- $filter
- );
+ if (!empty($extrafields->attributes[$objectforfieldstmp->table_element]['type'][$key]) && $extrafields->attributes[$objectforfieldstmp->table_element]['type'][$key] == 'link') {
+ if (!empty($extrafields->attributes[$objectforfieldstmp->table_element]['param'][$key]['options'])) {
+ $tmpextrafields = array_keys($extrafields->attributes[$objectforfieldstmp->table_element]['param'][$key]['options']);
+ $objectdesc = $tmpextrafields[0];
+ }
+ }
+ } else {
+ // For a property in ->fields
+ $objectdesc = $objectforfieldstmp->fields[$tmparray[1]]['type'];
+ $objectdesc = preg_replace('/^integer[^:]*:/', '', $objectdesc);
}
}
+
+ if ($objectdesc) {
+ // Example of value for $objectdesc:
+ // Bom:bom/class/bom.class.php:0:t.status=1
+ // Bom:bom/class/bom.class.php:0:t.status=1:ref
+ // Bom:bom/class/bom.class.php:0:(t.status:=:1) OR (t.field2:=:2):ref
+ $InfoFieldList = explode(":", $objectdesc, 4);
+ $vartmp = (empty($InfoFieldList[3]) ? '' : $InfoFieldList[3]);
+ $reg = array();
+ if (preg_match('/^.*:(\w*)$/', $vartmp, $reg)) {
+ $InfoFieldList[4] = $reg[1]; // take the sort field
+ }
+ $InfoFieldList[3] = preg_replace('/:\w*$/', '', $vartmp); // take the filter field
+
+ $classname = $InfoFieldList[0];
+ $classpath = $InfoFieldList[1];
+ //$addcreatebuttonornot = empty($InfoFieldList[2]) ? 0 : $InfoFieldList[2];
+ $filter = empty($InfoFieldList[3]) ? '' : $InfoFieldList[3];
+ $sortfield = empty($InfoFieldList[4]) ? '' : $InfoFieldList[4];
+
+ // Load object according to $id and $element
+ $objecttmp = fetchObjectByElement(0, strtolower($InfoFieldList[0]));
+
+ // Fallback to another solution to get $objecttmp
+ if (empty($objecttmp) && !empty($classpath)) {
+ dol_include_once($classpath);
+
+ if ($classname && class_exists($classname)) {
+ $objecttmp = new $classname($this->db);
+ }
+ }
+ }
+
+ // Make some replacement in $filter. May not be used if we used the ajax mode with $objectfield. In such a case
+ // we propagate the $objectfield and not the filter and replacement is done by the ajax/selectobject.php component.
+ $sharedentities = getEntity($objecttmp->element);
+ $filter = str_replace(
+ array('__ENTITY__', '__SHARED_ENTITIES__', '__USER_ID__'),
+ array($conf->entity, $sharedentities, $user->id),
+ $filter
+ );
+
if (!is_object($objecttmp)) {
- dol_syslog('Error bad setup of type for field ' . join(',', $InfoFieldList), LOG_WARNING);
- return 'Error bad setup of type for field ' . join(',', $InfoFieldList);
+ dol_syslog('selectForForms: Error bad setup of field objectdescorig=' . $objectdescorig.', objectfield='.$objectfield, LOG_WARNING);
+ return 'selectForForms: Error bad setup of field objectdescorig=' . $objectdescorig.', objectfield='.$objectfield;
}
//var_dump($filter);
@@ -8069,6 +8107,8 @@ class Form
$confkeyforautocompletemode = strtoupper($prefixforautocompletemode) . '_USE_SEARCH_TO_SELECT'; // For example COMPANY_USE_SEARCH_TO_SELECT
dol_syslog(get_class($this) . "::selectForForms filter=" . $filter, LOG_DEBUG);
+
+ // Generate the combo HTML component
$out = '';
if (!empty($conf->use_javascript_ajax) && getDolGlobalString($confkeyforautocompletemode) && !$forcecombo) {
// No immediate load of all database
@@ -8079,13 +8119,12 @@ class Form
//unset($objecttmp);
}
- $objectdesc = $classname . ':' . $classpath . ':' . $addcreatebuttonornot . ':' . $filter;
+ // Set url and param to call to get json of the search results
$urlforajaxcall = DOL_URL_ROOT . '/core/ajax/selectobject.php';
+ $urloption = 'htmlname=' . urlencode($htmlname) . '&outjson=1&objectdesc=' . urlencode($objectdescorig) . '&objectfield='.urlencode($objectfield) . ($sortfield ? '&sortfield=' . urlencode($sortfield) : '');
- // No immediate load of all database
- $urloption = 'htmlname=' . urlencode($htmlname) . '&outjson=1&objectdesc=' . urlencode($objectdesc) . '&filter=' . urlencode($filter) . ($sortfield ? '&sortfield=' . urlencode($sortfield) : '');
// Activate the auto complete using ajax call.
- $out .= ajax_autocompleter($preselectedvalue, $htmlname, $urlforajaxcall, $urloption, $conf->global->$confkeyforautocompletemode, 0, array());
+ $out .= ajax_autocompleter($preselectedvalue, $htmlname, $urlforajaxcall, $urloption, getDolGlobalString($confkeyforautocompletemode), 0, array());
$out .= '';
$out .= '';
} else {
@@ -8101,7 +8140,7 @@ class Form
* Output html form to select an object.
* Note, this function is called by selectForForms or by ajax selectobject.php
*
- * @param Object $objecttmp Object to knwo the table to scan for combo.
+ * @param Object $objecttmp Object to know the table to scan for combo.
* @param string $htmlname Name of HTML select component
* @param int $preselectedvalue Preselected value (ID of element)
* @param string $showempty ''=empty values not allowed, 'string'=value show if we allow empty values (for example 'All', ...)
@@ -8113,13 +8152,13 @@ class Form
* @param int $outputmode 0=HTML select string, 1=Array
* @param int $disabled 1=Html component is disabled
* @param string $sortfield Sort field
- * @param string $filter Add more filter
+ * @param string $filter Add more filter (Universal Search Filter)
* @return string|array Return HTML string
* @see selectForForms()
*/
public function selectForFormsList($objecttmp, $htmlname, $preselectedvalue, $showempty = '', $searchkey = '', $placeholder = '', $morecss = '', $moreparams = '', $forcecombo = 0, $outputmode = 0, $disabled = 0, $sortfield = '', $filter = '')
{
- global $conf, $langs, $user, $hookmanager;
+ global $langs, $user, $hookmanager;
//print "$htmlname, $preselectedvalue, $showempty, $searchkey, $placeholder, $morecss, $moreparams, $forcecombo, $outputmode, $disabled";
@@ -8239,7 +8278,7 @@ class Form
$textifempty = ' ';
//if (!empty($conf->use_javascript_ajax) || $forcecombo) $textifempty='';
- if (!empty($conf->global->$confkeyforautocompletemode)) {
+ if (getDolGlobalInt($confkeyforautocompletemode)) {
if ($showempty && !is_numeric($showempty)) {
$textifempty = $langs->trans($showempty);
} else {
@@ -8289,7 +8328,7 @@ class Form
if (!$forcecombo) {
include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
- $out .= ajax_combobox($htmlname, null, (!empty($conf->global->$confkeyforautocompletemode) ? $conf->global->$confkeyforautocompletemode : 0));
+ $out .= ajax_combobox($htmlname, null, getDolGlobalInt($confkeyforautocompletemode, 0));
}
} else {
dol_print_error($this->db);
diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php
index de24f13997f..a1cb88fa60e 100644
--- a/htdocs/core/lib/functions.lib.php
+++ b/htdocs/core/lib/functions.lib.php
@@ -12134,7 +12134,7 @@ function getElementProperties($element_type)
* Fetch an object from its id and element_type
* Inclusion of classes is automatic
*
- * @param int $element_id Element id
+ * @param int $element_id Element id (Use this or element_id but not both)
* @param string $element_type Element type ('module' or 'myobject@mymodule' or 'mymodule_myobject')
* @param string $element_ref Element ref (Use this or element_id but not both)
* @return int|object object || 0 || <0 if error
@@ -12154,13 +12154,17 @@ function fetchObjectByElement($element_id, $element_type, $element_ref = '')
if (class_exists($element_prop['classname'])) {
$classname = $element_prop['classname'];
$objecttmp = new $classname($db);
- $ret = $objecttmp->fetch($element_id, $element_ref);
- if ($ret >= 0) {
- if (empty($objecttmp->module)) {
- $objecttmp->module = $element_prop['module'];
- }
- return $objecttmp;
+ if ($element_id > 0 || !empty($element_ref)) {
+ $ret = $objecttmp->fetch($element_id, $element_ref);
+ if ($ret >= 0) {
+ if (empty($objecttmp->module)) {
+ $objecttmp->module = $element_prop['module'];
+ }
+ return $objecttmp;
+ }
+ } else {
+ return $objecttmp; // returned an object without fetch
}
} else {
return -1;