diff --git a/htdocs/core/ajax/ajaxextrafield.php b/htdocs/core/ajax/ajaxextrafield.php new file mode 100644 index 00000000000..43b43fd1094 --- /dev/null +++ b/htdocs/core/ajax/ajaxextrafield.php @@ -0,0 +1,342 @@ + + * Copyright (C) 2024 Frédéric France + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file htdocs/core/ajax/ajaxextrafield.php + * \ingroup extrafield + * \brief This script returns content of extrafield + */ + +if (!defined('NOTOKENRENEWAL')) { + // Disables token renewal + define('NOTOKENRENEWAL', 1); +} +if (!defined('NOREQUIREMENU')) { + define('NOREQUIREMENU', '1'); +} +if (!defined('NOREQUIREHTML')) { + define('NOREQUIREHTML', '1'); +} +if (!defined('NOREQUIREAJAX')) { + define('NOREQUIREAJAX', '1'); +} +if (!defined('NOHEADERNOFOOTER')) { + define('NOHEADERNOFOOTER', '1'); +} + +include '../../main.inc.php'; +include_once DOL_DOCUMENT_ROOT . '/core/class/html.form.class.php'; +/** + * @var Conf $conf + * @var DoliDB $db + * @var Translate $langs + * @var User $user + */ + +// object id +$objectid = GETPOST('objectid', 'aZ09'); +// 'module' or 'myobject@mymodule', 'mymodule_myobject' +$objecttype = GETPOST('objecttype', 'aZ09arobase'); +$objectkey = GETPOST('objectkey', 'restricthtml'); +$search = GETPOST('search', 'restricthtml'); +$page = GETPOSTINT('page'); +$mode = GETPOSTINT('mode'); +$value = GETPOST('value', 'alphanohtml'); +$limit = 10; +$offset = (($page - 1) * $limit); +$element_ref = ''; +if (is_numeric($objectid)) { + $objectid = (int) $objectid; +} else { + $element_ref = $objectid; + $objectid = 0; +} +// Load object according to $element +$object = fetchObjectByElement($objectid, $objecttype, $element_ref); +if (empty($object->element)) { + httponly_accessforbidden('Failed to get object with fetchObjectByElement(id=' . $objectid . ', objecttype=' . $objecttype . ')'); +} + +$module = $object->module; +$element = $object->element; + +$usesublevelpermission = ($module != $element ? $element : ''); +if ($usesublevelpermission && !$user->hasRight($module, $element)) { // There is no permission on object defined, we will check permission on module directly + $usesublevelpermission = ''; +} + +// print $object->id.' - '.$object->module.' - '.$object->element.' - '.$object->table_element.' - '.$usesublevelpermission."\n"; + +// Security check +restrictedArea($user, $object->module, $object, $object->table_element, $usesublevelpermission); + + +/* + * View + */ + +top_httphead(); + +$data = [ + 'results' => [], + 'pagination' => [ + 'more' => true, + ] +]; +if ($page == 1) { + $data['results'][] = [ + 'id' => -1, + 'text' => ' ', + ]; +} +$i = 0; +if ($object instanceof CommonObject) { + $extrafields = new ExtraFields($db); + $extrafields->fetch_name_optionals_label($element); + $options = $extrafields->attributes[$element]['param'][$objectkey]['options']; + if (is_array($options)) { + $tmpparamoptions = array_keys($options); + $paramoptions = preg_split('/[\r\n]+/', $tmpparamoptions[0]); + + $InfoFieldList = explode(":", $paramoptions[0], 5); + // 0 : tableName + // 1 : label field name + // 2 : key fields name (if different of rowid) + // optional parameters... + // 3 : key field parent (for dependent lists). How this is used ? + // 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value. Or use USF on the second line. + // 5 : string category type. This replace the filter. + // 6 : ids categories list separated by comma for category root. This replace the filter. + // 7 : sort field (not used here but used into format for commobject) + + // If there is a filter, we extract it by taking all content inside parenthesis. + if (! empty($InfoFieldList[4])) { + $pos = 0; // $pos will be position of ending filter + $parenthesisopen = 0; + while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) { + if (substr($InfoFieldList[4], $pos, 1) == '(') { + $parenthesisopen++; + } + if (substr($InfoFieldList[4], $pos, 1) == ')') { + $parenthesisopen--; + } + $pos++; + } + $tmpbefore = substr($InfoFieldList[4], 0, $pos); + $tmpafter = substr($InfoFieldList[4], $pos + 1); + //var_dump($InfoFieldList[4].' -> '.$pos); var_dump($tmpafter); + $InfoFieldList[4] = $tmpbefore; + if ($tmpafter !== '') { + $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter)); + } + + // Fix better compatibility with some old extrafield syntax filter "(field=123)" + $reg = array(); + if (preg_match('/^\(?([a-z0-9]+)([=<>]+)(\d+)\)?$/i', $InfoFieldList[4], $reg)) { + $InfoFieldList[4] = '(' . $reg[1] . ':' . $reg[2] . ':' . $reg[3] . ')'; + } + + //var_dump($InfoFieldList); + } + + $parentName = ''; + $parentField = ''; + $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2] . ' as rowid'); + + if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { + list($parentName, $parentField) = explode('|', $InfoFieldList[3]); + $keyList .= ', ' . $parentField; + } + if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) { + if (strpos($InfoFieldList[4], 'extra.') !== false) { + $keyList = 'main.' . $InfoFieldList[2] . ' as rowid'; + } else { + $keyList = $InfoFieldList[2] . ' as rowid'; + } + } + + $filter_categorie = false; + if (count($InfoFieldList) > 5) { + if ($InfoFieldList[0] == 'categorie') { + $filter_categorie = true; + } + } + + if (!$filter_categorie) { + $fields_label = explode('|', $InfoFieldList[1]); + if (count($fields_label) > 0) { + $keyList .= ', '; + $keyList .= implode(', ', $fields_label); + } + + $sqlwhere = ''; + $sql = "SELECT " . $keyList; + $sql .= ' FROM ' . $db->prefix() . $InfoFieldList[0]; + + // Add filter from 4th field + if (!empty($InfoFieldList[4])) { + $tags = []; + preg_match_all('/\$(.*?)\$/', $InfoFieldList[4], $tags); + foreach ($tags[0] as $keytag => $valuetag) { + $property = strtolower($tags[1][$keytag]); + if (strpos($InfoFieldList[4], $valuetag) !== false && property_exists($object, $property) && !empty($object->$property)) { + $InfoFieldList[4] = str_replace($valuetag, (string) $object->$property, $InfoFieldList[4]); + } else { + $InfoFieldList[4] = str_replace($valuetag, '0', $InfoFieldList[4]); + } + } + // can use current entity filter + if (strpos($InfoFieldList[4], '$ENTITY$') !== false) { + $InfoFieldList[4] = str_replace('$ENTITY$', (string) $conf->entity, $InfoFieldList[4]); + } + // can use SELECT request + if (strpos($InfoFieldList[4], '$SEL$') !== false) { + $InfoFieldList[4] = str_replace('$SEL$', 'SELECT', $InfoFieldList[4]); + } + // can use MODE request (list or view) + if (strpos($InfoFieldList[4], '$MODE$') !== false) { + $InfoFieldList[4] = str_replace('$MODE$', (string) $mode, $InfoFieldList[4]); + } + + // current object id can be use into filter + if (strpos($InfoFieldList[4], '$ID$') !== false && !empty($objectid)) { + $InfoFieldList[4] = str_replace('$ID$', (string) $objectid, $InfoFieldList[4]); + } else { + $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]); + } + + // We have to join on extrafield table + $errstr = ''; + if (strpos($InfoFieldList[4], 'extra.') !== false) { + $sql .= ' as main, ' . $db->sanitize($db->prefix() . $InfoFieldList[0]) . '_extrafields as extra'; + $sqlwhere .= " WHERE extra.fk_object = main." . $db->sanitize($InfoFieldList[2]); + $sqlwhere .= " AND " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1); + } else { + $sqlwhere .= " WHERE " . forgeSQLFromUniversalSearchCriteria($InfoFieldList[4], $errstr, 1); + } + } else { + $sqlwhere .= ' WHERE 1=1'; + } + + // Some tables may have field, some other not. For the moment we disable it. + if (in_array($InfoFieldList[0], array('tablewithentity'))) { + $sqlwhere .= ' AND entity = ' . ((int) $conf->entity); + } + if ($search) { + if ($fields_label) { + $sqlwhere .= " " . natural_search($fields_label, $search, 0); + } + } + $sql .= $sqlwhere; + $orderfields = explode('|', $InfoFieldList[1]); + $keyList = $InfoFieldList[1]; + if (count($orderfields)) { + $keyList = implode(', ', $orderfields); + } + $sql .= $db->order($keyList); + $sql .= $db->plimit($limit, $offset); + + $data['sql'] = $sql; + + $resql = $db->query($sql); + if ($resql) { + // $out .= ''; + $num = $db->num_rows($resql); + $i = 0; + while ($i < $num) { + $labeltoshow = ''; + $obj = $db->fetch_object($resql); + + // Several field into label (eq table:code|label:rowid) + $notrans = false; + $fields_label = explode('|', $InfoFieldList[1]); + if (count($fields_label) > 1) { + $notrans = true; + foreach ($fields_label as $field_toshow) { + $labeltoshow .= $obj->$field_toshow . ' '; + } + } else { + $labeltoshow = $obj->{$InfoFieldList[1]}; + } + + if ($value == $obj->rowid) { + if (!$notrans) { + foreach ($fields_label as $field_toshow) { + $translabel = $langs->trans($obj->$field_toshow); + $labeltoshow = $translabel . ' '; + } + } + // $out .= ''; + $data['results'][] = [ + 'id' => $obj->rowid, + 'text' => $labeltoshow, + ]; + } else { + if (!$notrans) { + $translabel = $langs->trans($obj->{$InfoFieldList[1]}); + $labeltoshow = $translabel; + } + if (empty($labeltoshow)) { + $labeltoshow = '(not defined)'; + } + + if (!empty($InfoFieldList[3]) && $parentField) { + $parent = $parentName . ':' . $obj->{$parentField}; + } + + // $out .= ''; + $data['results'][] = [ + 'id' => $obj->rowid, + 'text' => $labeltoshow, + ]; + } + + $i++; + } + $db->free($resql); + } else { + dol_syslog('Error in request ' . $db->lasterror() . '. Check setup of extra parameters.', LOG_ERR); + } + } else { + require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php'; + require_once DOL_DOCUMENT_ROOT . '/core/class/html.form.class.php'; + $form = new Form($db); + $categories = $form->select_all_categories(Categorie::$MAP_ID_TO_CODE[$InfoFieldList[5]], '', 'parent', 64, $InfoFieldList[6], 1, 1); + // $out .= ''; + // if (is_array($categories)) { + // foreach ($categories as $category_key => $category_value) { + // $out .= ''; + $out .= ''; + } elseif (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2')) { + include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php'; + $out .= ajax_combobox($keyprefix.$key.$keysuffix, array(), 0); + } + } + if (!getDolGlobalString('MAIN_EXTRAFIELDS_ENABLE_NEW_SELECT2')) { + $out .= ''; } - $out .= ''; } elseif ($type == 'checkbox') { $value_arr = $value; if (!is_array($value)) { diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 60715181d98..ab81f4966c6 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -1585,16 +1585,16 @@ function dol_get_object_properties($obj, $properties = []) /** - * Create a clone of instance of object (new instance with same value for each properties) + * Create a clone of instance of object (new instance with same value for each properties) * With native = 0: Property that are references are different memory area in the new object (full isolation clone). This means $this->object of new object may not be valid (except this->db that is voluntarly kept). * With native = 1: Use PHP clone. Property that are reference are same pointer. This means $this->db of new object is still valid but point to same this->db than original object. * With native = 2: Property that are reference are different memory area in the new object (full isolation clone). Only scalar and array values are cloned. This means method are not availables and $this->db of new object is not valid. * * @template T * - * @param T $object Object to clone + * @param T $object Object to clone * @param int $native 0=Full isolation method, 1=Native PHP method, 2=Full isolation method keeping only scalar and array properties (recommended) - * @return T Clone object + * @return T Clone object * * @see https://php.net/manual/language.oop5.cloning.php * @phan-suppress PhanTypeExpectedObjectPropAccess