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