dolibarr 24.0.0-beta
commonsellistfield.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2025 Open-Dsi <support@open-dsi.fr>
3 * Copyright (C) 2026 MDW <mdeweerd@users.noreply.github.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
25require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php';
26
27
32{
36 public static $ajaxUrl = DOL_URL_ROOT . '/core/ajax/ajaxfield.php';
37
41 public static $options = array();
42
46 const MAP_ID_TO_CODE = array(
47 0 => 'product',
48 1 => 'supplier',
49 2 => 'customer',
50 3 => 'member',
51 4 => 'contact',
52 5 => 'bank_account',
53 6 => 'project',
54 7 => 'user',
55 8 => 'bank_line',
56 9 => 'warehouse',
57 10 => 'actioncomm',
58 11 => 'website_page',
59 12 => 'ticket',
60 13 => 'knowledgemanagement',
61 14 => 'fichinter',
62 16 => 'order',
63 17 => 'invoice',
64 20 => 'supplier_order',
65 21 => 'supplier_invoice'
66 );
67
74 public function getOptionsParams($options)
75 {
76 $options = is_array($options) ? $options : array();
77 $paramList = array_keys($options);
78 $paramList = preg_split('/[\r\n]+/', $paramList[0]);
79 // 0 : tableName
80 // 1 : label field name
81 // 2 : key fields name (if different of rowid)
82 // optional parameters...
83 // 3 : key field parent (for dependent lists). (= 'parentName|parentField'; parentName: Name of the input field (ex: ref or options_code); parentField: Name of the field in the table for getting the value)
84 // Only the value who is equal to the selected value of the parentName input with the value of the parentField is displayed in this select options
85 // 4 : where clause filter on column or table extrafield, syntax field='value' or extra.field=value. Or use USF on the second line.
86 // 5 : string category type. This replace the filter.
87 // 6 : ids categories list separated by comma for category root. This replace the filter.
88 // 7 : sort field (Don't manage ASC or DESC)
89
90 $all = (string) $paramList[0];
91 $InfoFieldList = explode(":", $all, 5);
92
93 // If there is a filter, we extract it by taking all content inside parenthesis.
94 if (!empty($InfoFieldList[4])) {
95 $pos = 0; // $pos will be position of ending filter
96 $parenthesisopen = 0;
97 while (substr($InfoFieldList[4], $pos, 1) !== '' && ($parenthesisopen || $pos == 0 || substr($InfoFieldList[4], $pos, 1) != ':')) {
98 if (substr($InfoFieldList[4], $pos, 1) == '(') {
99 $parenthesisopen++;
100 }
101 if (substr($InfoFieldList[4], $pos, 1) == ')') {
102 $parenthesisopen--;
103 }
104 $pos++;
105 }
106 $tmpbefore = substr($InfoFieldList[4], 0, $pos);
107 $tmpafter = substr($InfoFieldList[4], $pos + 1);
108 $InfoFieldList[4] = $tmpbefore;
109 if ($tmpafter !== '') {
110 $InfoFieldList = array_merge($InfoFieldList, explode(':', $tmpafter));
111 }
112
113 // Fix better compatibility with some old extrafield syntax filter "(field=123)"
114 $reg = array();
115 if (preg_match('/^\‍(?([a-z0-9]+)([=<>]+)(\d+)\‍)?$/i', $InfoFieldList[4], $reg)) {
116 $InfoFieldList[4] = '(' . $reg[1] . ':' . $reg[2] . ':' . $reg[3] . ')';
117 }
118 }
119
120 $tableName = (string) ($InfoFieldList[0] ?? '');
121 $labelFullFields = (string) ($InfoFieldList[1] ?? '');
122 // @phpstan-ignore-next-line
123 $labelFullFields = array_filter(array_map('trim', explode('|', $labelFullFields)), 'strlen');
124 $labelFields = array();
125 $labelAlias = array();
126 foreach ($labelFullFields as $labelFullField) {
127 $tmp = $this->getSqlFieldInfo($labelFullField);
128 $labelFields[] = $tmp['field'];
129 $labelAlias[] = $tmp['alias'];
130 }
131 $keyField = (string) ($InfoFieldList[2] ?? '');
132 if (empty($keyField)) {
133 $keyField = 'rowid';
134 }
135 $keyFieldParent = (string) ($InfoFieldList[3] ?? '');
136 $tmp = array_map('trim', explode('|', $keyFieldParent));
137 $parentName = (string) ($tmp[0] ?? '');
138 $parentFullField = (string) ($tmp[1] ?? '');
139 $tmp = $this->getSqlFieldInfo($parentFullField);
140 $parentField = $tmp['field'];
141 $parentAlias = $tmp['alias'];
142 $filter = (string) ($InfoFieldList[4] ?? '');
143 $categoryType = (string) ($InfoFieldList[5] ?? '');
144 if (is_numeric($categoryType)) { // deprecated: must use the category code instead of id. For backward compatibility.
145 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
146 $categoryType = self::MAP_ID_TO_CODE[(int) $categoryType] ?? '';
147 }
148 $categoryRoots = (string) ($InfoFieldList[6] ?? '');
149 $sortField = (string) ($InfoFieldList[7] ?? '');
150
151 return array(
152 'all' => $all,
153 'tableName' => $tableName,
154 'labelFullFields' => $labelFullFields,
155 'labelFields' => $labelFields,
156 'labelAlias' => $labelAlias,
157 'keyField' => $keyField,
158 'parentName' => $parentName,
159 'parentFullField' => $parentFullField,
160 'parentField' => $parentField,
161 'parentAlias' => $parentAlias,
162 'filter' => $filter,
163 'categoryType' => $categoryType,
164 'categoryRoots' => $categoryRoots,
165 'sortField' => $sortField,
166 );
167 }
168
175 public function getSqlFieldInfo($fullField)
176 {
177 if (preg_match('/(.*)\s+AS\s+(\w+)$/i', $fullField, $matches)) {
178 $field = $matches[1];
179 $alias = $matches[2];
180 } else {
181 $field = $fullField;
182 $alias = $fullField;
183 }
184
185 if (preg_match('/^\w+\.(.*)/i', $field, $matches)) {
186 $alias = $matches[1];
187 }
188
189 return array(
190 'field' => $field,
191 'alias' => $alias,
192 );
193 }
194
205 public function getOptions($fieldInfos, $key, $addEmptyValue = false, $reload = false, $selectedValues = array())
206 {
207 global $conf, $langs;
208
209 $selectedValues = array_map('trim', is_array($selectedValues) ? $selectedValues : array($selectedValues));
210
211 if (!isset(self::$options[$key]) || $reload) {
212 $options = array();
213 if (!empty($fieldInfos->options) && is_array($fieldInfos->options)) {
214 $optionsParams = $this->getOptionsParams($fieldInfos->options);
215
216 if ($optionsParams['tableName'] == 'categorie' && !empty($optionsParams['categoryType'])) {
217 $data = self::$form->select_all_categories($optionsParams['categoryType'], '', 'parent', 64, $optionsParams['categoryRoots'], 1, 1);
218 if (is_array($data)) {
219 foreach ($data as $data_key => $data_value) {
220 $options[$data_key] = array(
221 'label' => $data_value,
222 'parent' => '',
223 );
224 }
225 }
226 } else {
227 $filter = $optionsParams['filter'];
228 $hasExtra = !empty($filter) && strpos($filter, 'extra.') !== false;
229 $keyField = ($hasExtra ? 'main.' : '') . $optionsParams['keyField'];
230
231 $keyList = $keyField . ' AS rowid';
232 if (!empty($optionsParams['parentFullField'])) {
233 $keyList .= ', ' . $optionsParams['parentFullField'];
234 }
235 if (!empty($optionsParams['labelFullFields'])) {
236 $keyList .= ', ' . implode(', ', $optionsParams['labelFullFields']);
237 }
238
239 $sql = "SELECT " . $this->db->sanitize($keyList, 0, 0, 1);
240 $sql .= " FROM " . $this->db->sanitize($this->db->prefix() . $optionsParams['tableName']);
241 if ($hasExtra) {
242 $sql .= " AS main";
243 $sql .= " LEFT JOIN " . $this->db->sanitize($this->db->prefix() . $optionsParams['tableName']) . "_extrafields AS extra ON extra.fk_object = " . $keyField;
244 }
245
246 // Add filter from 4th field
247 if (!empty($filter)) {
248 // can use current entity filter
249 if (strpos($filter, '$ENTITY$') !== false) {
250 $filter = str_replace('$ENTITY$', (string) $conf->entity, $filter);
251 }
252 // can use SELECT request
253 global $dolibarr_allow_unsecured_select_in_extrafields_filter;
254 if (strpos($filter, '$SEL$') !== false && !empty($dolibarr_allow_unsecured_select_in_extrafields_filter)) {
255 $filter = str_replace('$SEL$', 'SELECT', $filter);
256 }
257 // can use MODE parameter (list or view)
258 if (strpos($filter, '$MODE$') !== false) {
259 $filter = str_replace('$MODE$', empty($fieldInfos->mode) ? 'view' : $fieldInfos->mode, $filter);
260 }
261
262 // Current object id can be used into filter
263 $objectid = isset($fieldInfos->otherParams['objectId']) ? (int) $fieldInfos->otherParams['objectId'] : (isset($fieldInfos->object) && is_object($fieldInfos->object) ? (int) $fieldInfos->object->id : 0);
264 if (strpos($filter, '$ID$') !== false && !empty($objectid)) {
265 $filter = str_replace('$ID$', (string) $objectid, $filter);
266 } elseif (substr($_SERVER["PHP_SELF"], -8) == 'list.php') {
267 // In filters of list views, we do not want $ID$ replaced by 0. So we remove the '=' condition.
268 // Do nothing if condition is using 'IN' keyword
269 // Replace 'column = $ID$' by "word"
270 $filter = preg_replace('#\b([a-zA-Z0-9-\.-_]+)\b *= *\$ID\$#', '$1', $filter);
271 // Replace '$ID$ = column' by "word"
272 $filter = preg_replace('#\$ID\$ *= *\b([a-zA-Z0-9-\.-_]+)\b#', '$1', $filter);
273 } else {
274 $filter = str_replace('$ID$', '0', $filter);
275 }
276
277 // can use filter on any field of object
278 if (isset($fieldInfos->object) && is_object($fieldInfos->object)) {
279 $object = $fieldInfos->object;
280 $tags = [];
281 preg_match_all('/\$(.*?)\$/', $filter, $tags); // Example: $filter is ($dateadh$:<=:CURRENT_DATE)
282 foreach ($tags[0] as $keytag => $valuetag) {
283 $property = preg_replace('/[^a-z0-9_]/', '', strtolower($tags[1][$keytag]));
284 if (strpos($filter, $valuetag) !== false && property_exists($object, $property) && !empty($object->$property)) {
285 $filter = str_replace($valuetag, (string) $object->$property, $filter);
286 } else {
287 $filter = str_replace($valuetag, '0', $filter);
288 }
289 }
290 }
291
292 $errstr = '';
293 $sql .= " WHERE " . forgeSQLFromUniversalSearchCriteria($filter, $errstr, 1);
294 } else {
295 $sql .= ' WHERE 1=1';
296 }
297 // Some tables may have field, some other not. For the moment we disable it.
298 if (in_array($optionsParams['tableName'], array('tablewithentity'))) {
299 $sql .= " AND entity = " . ((int) $conf->entity);
300 }
301 // Manage dependency list (from AJAX)
302 if (isset($fieldInfos->optionsSqlDependencyValue)) {
303 // TODO rework for dependency with a date or a multiselect
304 $sql .= " AND " . $this->db->sanitize($optionsParams['parentField']) . " = '" . $this->db->escape($fieldInfos->optionsSqlDependencyValue) . "'";
305 }
306 // Only selected values
307 if (!empty($selectedValues)) {
308 $sanitizedSqlIn = "'" . implode("','", array_map(array($this->db, 'escape'), $selectedValues)) . "'";
309 $sql .= " AND " . $this->db->sanitize($keyField) . " IN (" . $sanitizedSqlIn . ")";
310 }
311
312 // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]'
313 if (preg_match('/^[a-z0-9_\-,]+$/i', $optionsParams['sortField'])) {
314 $sql .= $this->db->order($optionsParams['sortField']);
315 } else {
316 $sql .= $this->db->order(implode(', ', $optionsParams['labelFields']));
317 }
318
319 $limit = getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', $fieldInfos->optionsSqlLimit ?? 1000);
320 $offset = $fieldInfos->optionsSqlOffset ?? (isset($fieldInfos->optionsSqlPage) ? (((int) $fieldInfos->optionsSqlPage) - 1) * $limit : 0);
321 $sql .= $this->db->plimit($limit, $offset);
322
323 dol_syslog(get_class($this) . '::getOptions', LOG_DEBUG);
324 $resql = $this->db->query($sql);
325 if ($resql) {
326 while ($obj = $this->db->fetch_object($resql)) {
327 $optionKey = (string) $obj->rowid;
328
329 $toPrint = array();
330 foreach ($optionsParams['labelAlias'] as $fieldToShow) {
331 $toPrint[] = is_string($obj->$fieldToShow) ? $langs->trans($obj->$fieldToShow) : $obj->$fieldToShow;
332 }
333 $optionLabel = implode(' ', $toPrint);
334
335 if (empty($optionLabel)) {
336 $optionLabel = '(not defined)';
337 }
338
339 // Manage dependency list
340 $fieldValueParent = !empty($optionsParams['parentName']) && !empty($optionsParams['parentAlias']) ? $optionsParams['parentName'] . ':' . ((string) $obj->{$optionsParams['parentAlias']}) : '';
341
342 $options[$optionKey] = array(
343 'id' => $optionKey,
344 'label' => $optionLabel,
345 'parent' => $fieldValueParent,
346 );
347 }
348 $this->db->free($resql);
349 } else {
350 $this->error = 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.<br>';
351 return null;
352 }
353 }
354 }
355 if ($addEmptyValue && (!$fieldInfos->required || count($options) > 1)) {
356 // For preserve the numeric key indexes
357 $options = array(
358 '' => array(
359 'id' => '',
360 'label' => '&nbsp;',
361 'parent' => '',
362 )
363 ) + $options;
364 }
365
366 self::$options[$key] = $options;
367 }
368
369 $options = self::$options[$key];
370 // Only selected values
371 if (!empty($selectedValues)) {
372 $options = array_intersect_key($options, array_flip($selectedValues));
373 }
374
375 return $options;
376 }
377}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
Class to common field.
Class to common sellist field.
getOptionsParams($options)
Get all parameters in the options.
getSqlFieldInfo($fullField)
Get sql info of the full field.
getOptions($fieldInfos, $key, $addEmptyValue=false, $reload=false, $selectedValues=array())
Get list of options.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php