* Copyright (C) 2002-2003 Jean-Louis Bergamo * Copyright (C) 2004 Sebastien Di Cintio * Copyright (C) 2004 Benoit Mortier * Copyright (C) 2009-2012 Laurent Destailleur * Copyright (C) 2009-2012 Regis Houssin * Copyright (C) 2013 Florian Henry * Copyright (C) 2015 Charles-Fr BENKE * Copyright (C) 2016 Raphaël Doursenaud * Copyright (C) 2017 Nicolas ZABOURI * Copyright (C) 2018-2022 Frédéric France * Copyright (C) 2022 Antonin MARCHAL * * 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/class/fieldsmanager.class.php * \ingroup core * \brief File of class to manage fields */ require_once DOL_DOCUMENT_ROOT . '/core/class/fieldinfos.class.php'; require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; /** * Class to manage fields */ class FieldsManager { /** * @var DoliDB Database handler. */ public $db; /** * @var string Error code (or message) */ public $error = ''; /** * @var string[] Array of Error code (or message) */ public $errors = array(); /** * @var array To store error results of ->validateField() */ public $validateFieldsErrors = array(); /** * @var string Path to fields classes */ public $fieldsPath = '/core/class/fields/'; /** * @var array Field classes cached */ public static $fieldClasses = array(); /** * @var array,extraField:array}>> Field infos cached (array,extraField:array}>>) */ public static $fieldInfos = array(); /** * @var array>|null Array with boolean of status of groups */ public $expand_display = array(); ///** // * @var array Array of type to label // */ //public static $type2label = array( // 'varchar' => 'String1Line', // 'text' => 'TextLongNLines', // 'html' => 'HtmlText', // 'int' => 'Int', // 'double' => 'Float', // 'date' => 'Date', // 'datetime' => 'DateAndTime', // 'duration' => 'Duration', // //'datetimegmt'=>'DateAndTimeUTC', // 'boolean' => 'Boolean', // 'price' => 'ExtrafieldPrice', // 'pricecy' => 'ExtrafieldPriceWithCurrency', // 'phone' => 'ExtrafieldPhone', // 'email' => 'ExtrafieldMail', // 'url' => 'ExtrafieldUrl', // 'ip' => 'ExtrafieldIP', // 'icon' => 'Icon', // 'password' => 'ExtrafieldPassword', // 'radio' => 'ExtrafieldRadio', // 'select' => 'ExtrafieldSelect', // 'sellist' => 'ExtrafieldSelectList', // 'checkbox' => 'ExtrafieldCheckBox', // 'chkbxlst' => 'ExtrafieldCheckBoxFromList', // 'link' => 'ExtrafieldLink', // 'point' => 'ExtrafieldPointGeo', // 'multipts' => 'ExtrafieldMultiPointGeo', // 'linestrg' => 'ExtrafieldLinestringGeo', // 'polygon' => 'ExtrafieldPolygonGeo', // 'separate' => 'ExtrafieldSeparator', // 'stars' => 'ExtrafieldStars', // //'real' => 'ExtrafieldReal', //); /** * Constructor * * @param DoliDB $db Database handler * @param Form|null $form Specific form handler */ public function __construct($db, $form = null) { $this->db = $db; $this->error = ''; $this->errors = array(); if (isset($form)) { CommonField::setForm($form); } } /** * Get field handler for the provided type * * @param string $type Field type * @return CommonField|null */ public function getFieldClass($type) { global $hookmanager, $langs; $type = trim($type); if (!isset(self::$fieldClasses[$type])) { $field = null; $parameters = array( 'type' => $type, // @phan-suppress-next-line PhanPluginConstantVariableNull 'field' => &$field, ); $hookmanager->executeHooks('getFieldClass', $parameters, $this); // Note that $object may have been modified by hook // @phpstan-ignore-next-line @phan-suppress-next-line PhanPluginConstantVariableNull if (isset($field) && is_object($field)) { self::$fieldClasses[$type] = $field; } else { $filename = strtolower($type) . 'field.class.php'; $classname = ucfirst($type) . 'Field'; // Load class file dol_include_once(rtrim($this->fieldsPath, '/') . '/' . $filename); if (!class_exists($classname)) { @include_once DOL_DOCUMENT_ROOT . '/core/class/fields/' . $filename; } if (class_exists($classname)) { self::$fieldClasses[$type] = new $classname($this->db); } else { $langs->load("errors"); $this->errors[] = $langs->trans('ErrorFieldClassNotFoundForClassName', $classname, $type); return null; } } } $field = self::$fieldClasses[$type]; $field->clearErrors(); return $field; } /** * Get all fields handler available * * @return array */ public function getAllFields() { // Todo to make return self::$fieldClasses; } /** * clear errors * * @return void */ public function clearErrors() { $this->error = ''; $this->errors = array(); } /** * Method to output saved errors * * @param string $separator Separator between each error * @return string String with errors */ public function errorsToString($separator = ', ') { return $this->error . (is_array($this->errors) ? (!empty($this->error) ? $separator : '') . implode($separator, $this->errors) : ''); } /** * clear validation message result for a field * * @param string $fieldKey Key of attribute to clear * @return void */ public function clearFieldError($fieldKey) { $this->error = ''; unset($this->validateFieldsErrors[$fieldKey]); } /** * set validation error message a field * * @param string $fieldKey Key of attribute * @param string $msg the field error message * @return void */ public function setFieldError($fieldKey, $msg = '') { global $langs; if (empty($msg)) { $msg = $langs->trans("UnknownError"); } $this->error = $this->validateFieldsErrors[$fieldKey] = $msg; } /** * get field error message * * @param string $fieldKey Key of attribute * @return string Error message of validation ('' if no error) */ public function getFieldError($fieldKey) { if (!empty($this->validateFieldsErrors[$fieldKey])) { return $this->validateFieldsErrors[$fieldKey]; } return ''; } /** * get field error icon * * @param string $fieldValidationErrorMsg message to add in tooltip * @return string html output */ public function getFieldErrorIcon($fieldValidationErrorMsg) { $out = ''; if (!empty($fieldValidationErrorMsg) && function_exists('getFieldErrorIcon')) { $out .= ' ' . getFieldErrorIcon($fieldValidationErrorMsg); } return $out; } /** * Get list of fields infos for the provided mode into X columns * * @param CommonObject $object Object handler * @param ExtraFields $extrafields ExtraFields handler * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') * @param int $nbColumn Split fields infos into X columns * @param array $breakKeys Key used for break on each column (ex: array(1 => 'total_ht', ...)) * @param array $params Other params * @return array{columns:array>,hiddenFields:array} List of fields info by column and hidden */ public function getAllFieldsInfos(&$object, &$extrafields = null, $mode = 'view', $nbColumn = 2, $breakKeys = array(), $params = array()) { global $hookmanager, $langs; // Get object fields $fields = $this->getAllObjectFieldsInfos($object, $mode, $params); // Old sort if (!getDolGlobalInt('MAIN_FIELDS_SORT_WITH_EXTRA_FIELDS')) { $fields = dol_sort_array($fields, 'position'); } // Get extra fields $fields2 = $this->getAllExtraFieldsInfos($object, $extrafields, $mode, $params); $fields = array_merge($fields, $fields2); // New sort if (getDolGlobalInt('MAIN_FIELDS_SORT_WITH_EXTRA_FIELDS')) { $fields = dol_sort_array($fields, 'position'); } // Split in columns $idxColumn = 1; $columns = array(); $hiddenFields = array(); $columns[$idxColumn] = array(); $nbVisibleFields = 0; foreach ($fields as $field) { if ($field->visible) { $nbVisibleFields++; } } $nbFieldsByColumn = ceil($nbVisibleFields / $nbColumn); $breakKey = $breakKeys[$idxColumn] ?? ''; $idxField = 0; foreach ($fields as $key => $field) { if ($idxColumn < $nbColumn && ((!empty($breakKey) && $key == $breakKey) || (empty($breakKey) && $idxField == $nbFieldsByColumn))) { $idxColumn++; $idxField = 0; $columns[$idxColumn] = array(); } if ($field->visible) { if ($field->type != 'separate') { $idxField++; } // Add field into column $columns[$idxColumn][$key] = $field; } else { $hiddenFields[$key] = $field; } } // Add column not created for ($idxColumn = 1; $idxColumn <= $nbColumn; $idxColumn++) { if (!isset($columns[$idxColumn])) { $columns[$idxColumn] = array(); } } $parameters = array( 'object' => &$object, 'extrafields' => &$extrafields, 'mode' => $mode, 'nbColumn' => $nbColumn, 'breakKeys' => $breakKeys, 'params' => $params, 'columns' => &$columns, 'hiddenFields' => &$hiddenFields, ); $hookmanager->executeHooks('getFieldInfosFromObjectField', $parameters, $this); // Note that $object may have been modified by hook return array( 'columns' => $columns, 'hiddenFields' => $hiddenFields, ); } /** * Get list of object fields infos * * @param CommonObject $object Object handler * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') * @param array $params Other params * @return array List of fields infos */ public function getAllObjectFieldsInfos(&$object, $mode = 'view', $params = array()) { global $hookmanager; // Get object fields $fields = array(); // @phpstan-ignore-next-line if (isset($object->fields) && is_array($object->fields)) { $keyPrefix = getDolGlobalInt('MAIN_FIELDS_NEW_OBJECT_KEY_PREFIX') ? 'object_' : ''; foreach ($object->fields as $key => $field) { $fieldInfos = $this->getFieldInfosFromObjectField($object, $key, $mode, $params); $fields[$keyPrefix . $key] = $fieldInfos; } } $parameters = array( 'object' => &$object, 'mode' => $mode, 'params' => $params, 'fields' => &$fields, ); $hookmanager->executeHooks('getAllObjectFieldsInfos', $parameters, $this); // Note that $object may have been modified by hook return $fields; } /** * Get list of extra fields infos * * @param CommonObject $object Object handler * @param ExtraFields $extrafields ExtraFields handler * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') * @param array $params Other params * @return array List of fields infos */ public function getAllExtraFieldsInfos(&$object, &$extrafields = null, $mode = 'view', $params = array()) { global $hookmanager; // Get extra fields $fields = array(); if (isset($extrafields->attributes[$object->table_element]) && is_array($extrafields->attributes[$object->table_element])) { if (isset($extrafields->attributes[$object->table_element]['label']) && is_array($extrafields->attributes[$object->table_element]['label'])) { $keyPrefix = 'options_'; foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) { $fieldInfos = $this->getFieldInfosFromExtraField($object, $extrafields, $key, $mode, $params); $fields[$keyPrefix . $key] = $fieldInfos; } } } $parameters = array( 'object' => &$object, 'extrafields' => &$extrafields, 'mode' => $mode, 'params' => $params, 'fields' => &$fields, ); $hookmanager->executeHooks('getAllExtraFieldsInfos', $parameters, $this); // Note that $object may have been modified by hook return $fields; } /** * Get list of fields infos for the provided mode into X columns * * @param string $key Field key (begin by object_ for object or options_ for extrafields) * @param CommonObject $object Object handler * @param ExtraFields $extrafields ExtraFields handler * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') * @param array $params Other params * @return FieldInfos|null Get field info or null if not found */ public function getFieldsInfos($key, &$object, &$extrafields = null, $mode = 'view', $params = array()) { global $langs; $fieldInfos = null; $patternObjectPrefix = getDolGlobalInt('MAIN_FIELDS_NEW_OBJECT_KEY_PREFIX') ? 'object_' : ''; if (preg_match('/^options_(.*)/i', $key, $matches)) { $fieldKey = $matches[1]; $fieldInfos = $this->getFieldInfosFromExtraField($object, $extrafields, $fieldKey, $mode, $params); } elseif (preg_match('/^' . $patternObjectPrefix . '(.*)/i', $key, $matches)) { $fieldKey = $matches[2]; $fieldInfos = $this->getFieldInfosFromObjectField($object, $fieldKey, $mode, $params); } return $fieldInfos; } /** * Get field infos from object field infos * * @param CommonObject $object Object handler * @param string $key Field key * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') * @param array $params Other params * @return FieldInfos|null Properties of the field or null if field not found */ public function getFieldInfosFromObjectField(&$object, $key, $mode = 'view', $params = array()) { global $hookmanager; if (!isset($object->fields[$key])) { return null; } if (isset(self::$fieldInfos[$object->element][$mode]['object'][$key])) { return self::$fieldInfos[$object->element][$mode]['object'][$key]; } $attributes = $object->fields[$key]; $fieldInfos = new FieldInfos(); $fieldInfos->fieldType = FieldInfos::FIELD_TYPE_OBJECT; $fieldInfos->originType = $attributes['type'] ?? ''; $fieldInfos->size = $attributes['length'] ?? ''; $fieldInfos->label = $attributes['label'] ?? ''; $fieldInfos->langFile = $attributes['langfile'] ?? ''; $fieldInfos->sqlAlias = $attributes['alias'] ?? null; $fieldInfos->picto = $attributes['picto'] ?? ''; $fieldInfos->position = $attributes['position'] ?? 0; $fieldInfos->required = ($attributes['notnull'] ?? 0) > 0; $fieldInfos->alwaysEditable = !empty($attributes['alwayseditable']); $fieldInfos->defaultValue = $attributes['default'] ?? ''; $fieldInfos->css = $attributes['css'] ?? ''; $fieldInfos->viewCss = $attributes['cssview'] ?? ''; $fieldInfos->listCss = $attributes['csslist'] ?? ''; $fieldInfos->inputPlaceholder = $attributes['placeholder'] ?? ''; $fieldInfos->help = $attributes['help'] ?? ''; $fieldInfos->listHelp = $attributes['helplist'] ?? ''; $fieldInfos->showOnComboBox = !empty($attributes['showoncombobox']); $fieldInfos->inputDisabled = !empty($attributes['disabled']); $fieldInfos->inputAutofocus = !empty($attributes['autofocusoncreate']) && $mode == 'create'; $fieldInfos->comment = $attributes['comment'] ?? ''; $fieldInfos->listTotalizable = !empty($attributes['isameasure']) && $attributes['isameasure'] == 1; $fieldInfos->validateField = !empty($attributes['validate']); $fieldInfos->copyToClipboard = $attributes['copytoclipboard'] ?? 0; $fieldInfos->tdCss = $attributes['tdcss'] ?? ''; $fieldInfos->multiInput = !empty($attributes['multiinput']); $fieldInfos->nameInClass = $attributes['nameinclass'] ?? $key; $fieldInfos->nameInTable = $attributes['nameintable'] ?? $key; $fieldInfos->getNameUrlParams = $attributes['get_name_url_params'] ?? null; $fieldInfos->showOnHeader = !empty($attributes['showonheader']); // TODO set nameinclass = "id" in fields "rowid" if ($fieldInfos->nameInClass == 'rowid') { $fieldInfos->nameInClass = 'id'; } $enabled = $attributes['enabled'] ?? '1'; $visibility = $attributes['visible'] ?? '1'; $perms = empty($attributes['noteditable']) ? '1' : '0'; $this->setCommonFieldInfos($fieldInfos, $object, $extrafields, $key, $mode, $enabled, $visibility, $perms, $params); // Special case that force options and type ($type can be integer, varchar, ...) if (!empty($attributes['arrayofkeyval']) && is_array($attributes['arrayofkeyval'])) { $fieldInfos->options = $attributes['arrayofkeyval']; // Special case that prevent to force $type to have multiple input @phan-suppress-next-line PhanTypeMismatchProperty if (!$fieldInfos->multiInput) { $fieldInfos->type = (($fieldInfos->type == 'checkbox') ? $fieldInfos->type : 'select'); } } $parameters = array( 'object' => &$object, 'key' => $key, 'mode' => $mode, 'fieldInfos' => &$fieldInfos, ); $hookmanager->executeHooks('getFieldInfosFromObjectField', $parameters, $this); // Note that $object may have been modified by hook self::$fieldInfos[$object->element][$mode]['object'][$key] = $fieldInfos; return $fieldInfos; } /** * Get field infos from extra field infos * * @param CommonObject $object Object handler * @param ExtraFields $extrafields Extrafields handler * @param string $key Field key * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') * @param array $params Other params * @return FieldInfos|null Properties of the field or null if not found */ public function getFieldInfosFromExtraField(&$object, &$extrafields, $key, $mode = 'view', $params = array()) { global $hookmanager; if (!isset($extrafields->attributes[$object->table_element]['label'][$key])) { return null; } if (isset(self::$fieldInfos[$object->element][$mode]['extraField'][$key])) { return self::$fieldInfos[$object->element][$mode]['extraField'][$key]; } $attributes = $extrafields->attributes[$object->table_element]; $fieldInfos = new FieldInfos(); $fieldInfos->fieldType = FieldInfos::FIELD_TYPE_EXTRA_FIELD; $fieldInfos->originType = $attributes['type'][$key] ?? ''; $fieldInfos->label = $attributes['label'][$key] ?? ''; $fieldInfos->position = $attributes['pos'][$key] ?? 0; $fieldInfos->required = !empty($attributes['required'][$key]); $fieldInfos->defaultValue = $attributes['default'][$key] ?? ''; $fieldInfos->css = $attributes['css'][$key] ?? ''; $fieldInfos->help = $attributes['help'][$key] ?? ''; $fieldInfos->size = $attributes['size'][$key] ?? ''; $fieldInfos->computed = $attributes['computed'][$key] ?? ''; $fieldInfos->unique = !empty($attributes['unique'][$key]); $fieldInfos->alwaysEditable = !empty($attributes['alwayseditable'][$key]); $fieldInfos->emptyOnClone = !empty($attributes['emptyonclone'][$key]); $fieldInfos->langFile = $attributes['langfile'][$key] ?? ''; $fieldInfos->printable = !empty($attributes['printable'][$key]); $fieldInfos->aiPrompt = $attributes['aiprompt'][$key] ?? ''; $fieldInfos->viewCss = $attributes['cssview'][$key] ?? ''; $fieldInfos->listCss = $attributes['csslist'][$key] ?? ''; $fieldInfos->listTotalizable = !empty($attributes['totalizable'][$key]); $fieldInfos->options = array_diff_assoc($attributes['param'][$key]['options'] ?? array(), array('' => null)); // For remove case when not defined $fieldInfos->nameInClass = $key; $fieldInfos->nameInTable = $key; $enabled = $attributes['enabled'][$key] ?? '1'; $visibility = $attributes['list'][$key] ?? '1'; $perms = $attributes['perms'][$key] ?? null; $this->setCommonFieldInfos($fieldInfos, $object, $extrafields, $key, $mode, $enabled, $visibility, $perms, $params); $parameters = array( 'object' => &$object, 'extraFields' => &$extrafields, 'key' => $key, 'mode' => $mode, 'fieldInfos' => &$fieldInfos, ); $hookmanager->executeHooks('getFieldInfosFromExtraField', $parameters, $this); // Note that $object may have been modified by hook self::$fieldInfos[$object->element][$mode]['extraField'][$key] = $fieldInfos; return $fieldInfos; } /** * Set common field infos * * @param FieldInfos $fieldInfos Field infos to set with common infos * @param CommonObject $object Object handler * @param ExtraFields $extrafields Extrafields handler * @param string $key Field key * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') * @param string $enabled Condition when the field must be managed (Example: 1 or 'getDolGlobalInt("MY_SETUP_PARAM")' or 'isModEnabled("multicurrency")' ...) * @param string $visibility Condition when the field must be visible (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form (not create). 5=Visible on list and view form (not create/not update). 6=visible on list and update/view form (not create). Using a negative value means field is not shown by default on list but can be selected for viewing) * @param string $perms Condition when the field must be editable * @param array $params Other params * @return void */ public function setCommonFieldInfos(&$fieldInfos, &$object, &$extrafields, $key, $mode = 'view', $enabled = '1', $visibility = '', $perms = null, $params = array()) { global $user; $fieldInfos->object = &$object; $fieldInfos->mode = preg_replace('/[^a-z0-9_]/i', '', $mode); $fieldInfos->type = $fieldInfos->originType; $fieldInfos->key = $key; $fieldInfos->otherParams = $params; if (preg_match('/^(integer|link):(.*):(.*):(.*):(.*)/i', $fieldInfos->originType, $reg)) { $fieldInfos->options = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] . ':' . $reg[5] => 'N'); $fieldInfos->type = 'link'; } elseif (preg_match('/^(integer|link):(.*):(.*):(.*)/i', $fieldInfos->originType, $reg)) { $fieldInfos->options = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] => 'N'); $fieldInfos->type = 'link'; } elseif (preg_match('/^(integer|link):(.*):(.*)/i', $fieldInfos->originType, $reg)) { $fieldInfos->options = array($reg[2] . ':' . $reg[3] . ($reg[1] == 'User' ? ':#getnomurlparam1=-1' : '') => 'N'); $fieldInfos->type = 'link'; } elseif (preg_match('/^(sellist):(.*):(.*):(.*):(.*)/i', $fieldInfos->originType, $reg)) { $fieldInfos->options = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] . ':' . $reg[5] => 'N'); $fieldInfos->type = 'sellist'; } elseif (preg_match('/^(sellist):(.*):(.*):(.*)/i', $fieldInfos->originType, $reg)) { $fieldInfos->options = array($reg[2] . ':' . $reg[3] . ':' . $reg[4] => 'N'); $fieldInfos->type = 'sellist'; } elseif (preg_match('/^(sellist):(.*):(.*)/i', $fieldInfos->originType, $reg)) { $fieldInfos->options = array($reg[2] . ':' . $reg[3] => 'N'); $fieldInfos->type = 'sellist'; } elseif (preg_match('/^chkbxlst:(.*)/i', $fieldInfos->originType, $reg)) { $fieldInfos->options = array($reg[1] => 'N'); $fieldInfos->type = 'chkbxlst'; } elseif (preg_match('/varchar\((\d+)\)/', $fieldInfos->originType, $reg)) { $fieldInfos->options = array(); $fieldInfos->type = 'varchar'; $fieldInfos->size = $reg[1]; $fieldInfos->maxLength = (int) $reg[1]; } elseif (preg_match('/varchar/', $fieldInfos->originType)) { $fieldInfos->options = array(); $fieldInfos->type = 'varchar'; } elseif (preg_match('/stars\((\d+)\)/', $fieldInfos->originType, $reg)) { $fieldInfos->options = array(); $fieldInfos->type = 'stars'; $fieldInfos->size = $reg[1]; } elseif (preg_match('/integer/', $fieldInfos->originType)) { $fieldInfos->type = 'int'; } elseif ($fieldInfos->originType == 'mail') { $fieldInfos->type = 'email'; } elseif (preg_match('/^(text):(.*)/i', $fieldInfos->originType, $reg)) { $fieldInfos->type = 'text'; $fieldInfos->getPostCheck = $reg[2]; } elseif (preg_match('/^(html):(.*)/i', $fieldInfos->originType, $reg)) { $fieldInfos->type = 'html'; $fieldInfos->getPostCheck = $reg[2]; } elseif (preg_match('/^double\(([0-9]+,[0-9]+)\)/', $fieldInfos->originType, $reg)) { $fieldInfos->type = 'double'; $fieldInfos->size = $reg[1]; } // Set visibility $visibility = (int) dol_eval((string) $visibility, 1, 1, '2'); $absVisibility = abs($visibility); $enabled = (int) dol_eval((string) $enabled, 1, 1, '2'); $fieldInfos->visible = true; if (empty($visibility) || empty($enabled) || ($mode == 'create' && !in_array($absVisibility, array(1, 3, 6))) || ($mode == 'edit' && !in_array($absVisibility, array(1, 3, 4))) || ($mode == 'view' && (!in_array($absVisibility, array(1, 3, 4, 5)) || $fieldInfos->showOnHeader)) || ($mode == 'list' && $absVisibility == 3) ) { $fieldInfos->visible = false; } // Set edit perms if (isset($perms)) { $perms = (int) dol_eval((string) $perms, 1, 1, '2'); } else { //TODO Improve element and rights detection $mappingKeyForPerm = array( 'fichinter' => 'ficheinter', 'product' => 'produit', 'project' => 'projet', 'order_supplier' => 'supplier_order', 'invoice_supplier' => 'supplier_invoice', 'shipping' => 'expedition', 'productlot' => 'stock', 'facturerec' => 'facture', 'mo' => 'mrp', 'salary' => 'salaries', 'member' => 'adherent', ); $keyForPerm = $mappingKeyForPerm[$object->element] ?? $object->element; $perms = false; if (isset($user->rights->$keyForPerm)) { $perms = $user->hasRight($keyForPerm, 'creer') || $user->hasRight($keyForPerm, 'create') || $user->hasRight($keyForPerm, 'write'); } if ($object->element == 'order_supplier' && !getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD')) { $perms = $user->hasRight('fournisseur', 'commande', 'creer'); } elseif ($object->element == 'invoice_supplier' && !getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD')) { $perms = $user->hasRight('fournisseur', 'facture', 'creer'); } elseif ($object->element == 'delivery') { $perms = $user->hasRight('expedition', 'delivery', 'creer'); } elseif ($object->element == 'contact') { $perms = $user->hasRight('societe', 'contact', 'creer'); } } // Manage always editable of extra field $isDraft = ((isset($object->statut) && $object->statut == 0) || (isset($object->status) && $object->status == 0)); if ($mode == 'view' && !$isDraft && !$fieldInfos->alwaysEditable) { $perms = false; } // Case visible only in view so not editable if ($mode == 'view' && $absVisibility == 5) { $perms = false; } // Case field computed if (!empty($fieldInfos->computed)) { $perms = false; } $fieldInfos->editable = !empty($perms); // Set list info 'checked' $fieldInfos->listChecked = $mode == 'list' && $visibility > 0; } /** * Set all values of the object (with extra field) from POST * * @param CommonObject $object Object handler * @param ExtraFields $extrafields Extrafields handler * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') * @param array $params Other params * @return int Result <0 if KO, >0 if OK */ public function setFieldValuesFromPost(&$object, &$extrafields, $keyPrefix = '', $keySuffix = '', $mode = 'view', $params = array()) { $result = $this->setObjectFieldValuesFromPost($object, $keyPrefix, $keySuffix, $mode, $params); $result2 = $this->setExtraFieldValuesFromPost($object, $extrafields, $keyPrefix, $keySuffix, $mode, $params); return $result > 0 && $result2 > 0 ? 1 : -1; } /** * Set all object values of the object from POST * * @param CommonObject $object Object handler * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') * @param array $params Other params * @return int Result <0 if KO, >0 if OK */ public function setObjectFieldValuesFromPost(&$object, $keyPrefix = '', $keySuffix = '', $mode = 'view', $params = array()) { $fields = $this->getAllObjectFieldsInfos($object, $mode, $params); $error = 0; foreach ($fields as $fieldKey => $fieldInfos) { $check = true; $key = $fieldInfos->nameInClass ?? $fieldInfos->key; if ($fieldInfos->visible) { $check = $this->verifyPostFieldValue($fieldInfos, $fieldKey, $keyPrefix, $keySuffix); } if ($check) { $object->$key = $this->getPostFieldValue($fieldInfos, $fieldKey, $object->$key, $keyPrefix, $keySuffix); } if (!$fieldInfos->visible) { $check = $this->verifyFieldValue($fieldInfos, $fieldKey, $object->$key); } if (!$check) { $error++; } } return $error ? -1 : 1; } /** * Set all extra field values of the object from POST * * @param CommonObject $object Object handler * @param ExtraFields $extrafields Extrafields handler * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) * @param string $mode Get the fields infos for the provided mode ('create', 'edit', 'view', 'list') * @param array $params Other params * @return int Result <0 if KO, >0 if OK */ public function setExtraFieldValuesFromPost(&$object, &$extrafields, $keyPrefix = '', $keySuffix = '', $mode = 'view', $params = array()) { $fields = $this->getAllExtraFieldsInfos($object, $extrafields, $mode, $params); $error = 0; foreach ($fields as $fieldKey => $fieldInfos) { $check = true; $key = 'options_' . ($fieldInfos->nameInClass ?? $fieldInfos->key); if ($fieldInfos->visible) { $check = $this->verifyPostFieldValue($fieldInfos, $fieldKey, $keyPrefix, $keySuffix); } if ($check) { $object->array_options[$key] = $this->getPostFieldValue($fieldInfos, $fieldKey, $object->array_options[$key], $keyPrefix, $keySuffix); } if (!$fieldInfos->visible) { $check = $this->verifyFieldValue($fieldInfos, $fieldKey, $object->array_options[$key]); } if (!$check) { $error++; } } return $error ? -1 : 1; } /** * Verify if the field value is valid * * @param FieldInfos $fieldInfos Properties of the field * @param string $key Key of attribute * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) * @return bool */ public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') { global $hookmanager; $result = true; if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 1 || getDolGlobalString('MAIN_ACTIVATE_VALIDATION_RESULT')) { $parameters = array( 'fieldInfos' => &$fieldInfos, 'key' => $key, 'keyPrefix' => $keyPrefix, 'keySuffix' => $keySuffix, ); $reshook = $hookmanager->executeHooks('verifyPostFieldValue', $parameters, $this); // Note that $object may have been modified by hook if ($reshook > 0) { return (bool) $hookmanager->resPrint; } $this->clearErrors(); $field = $this->getFieldClass($fieldInfos->type); if (isset($field)) { $this->clearFieldError($key); $result = $field->verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); if (!$result) { $this->setFieldError($key, CommonField::$validator->error); } } } return $result; } /** * Verify if the field value is valid * * @param FieldInfos $fieldInfos Properties of the field * @param string $key Key of field * @param mixed $value Value to check (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) * @return bool */ public function verifyFieldValue($fieldInfos, $key, $value) { global $hookmanager; $result = true; if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 1 || getDolGlobalString('MAIN_ACTIVATE_VALIDATION_RESULT')) { $parameters = array( 'fieldInfos' => &$fieldInfos, 'key' => $key, 'value' => $value, ); $reshook = $hookmanager->executeHooks('verifyFieldValue', $parameters, $this); // Note that $object may have been modified by hook if ($reshook > 0) { return (bool) $hookmanager->resPrint; } $this->clearErrors(); $field = $this->getFieldClass($fieldInfos->type); if (isset($field)) { $this->clearFieldError($key); $result = $field->verifyFieldValue($fieldInfos, $key, $value); if (!$result) { $this->setFieldError($key, CommonField::$validator->error); } } } return $result; } /** * Get field value from GET/POST * * @param FieldInfos $fieldInfos Properties of the field * @param string $key Key of field * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) * @return mixed */ public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') { global $hookmanager; $value = ''; $parameters = array( 'fieldInfos' => &$fieldInfos, 'key' => $key, 'value' => &$value, 'keyPrefix' => $keyPrefix, 'keySuffix' => $keySuffix, ); $reshook = $hookmanager->executeHooks('getPostFieldValue', $parameters, $this); // Note that $object may have been modified by hook if ($reshook > 0) { return $value; } $this->clearErrors(); $field = $this->getFieldClass($fieldInfos->type); if (isset($field)) { $value = $field->getPostFieldValue($fieldInfos, $key, $defaultValue, $keyPrefix, $keySuffix); } else { $value = $defaultValue; } return $value; } /** * Get search field value from GET/POST * * @param FieldInfos $fieldInfos Properties of the field * @param string $key Key of field * @param mixed $defaultValue Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) * @return mixed */ public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') { global $hookmanager; $value = ''; $parameters = array( 'fieldInfos' => &$fieldInfos, 'key' => $key, 'value' => &$value, 'keyPrefix' => $keyPrefix, 'keySuffix' => $keySuffix, ); $reshook = $hookmanager->executeHooks('getPostSearchFieldValue', $parameters, $this); // Note that $object may have been modified by hook if ($reshook > 0) { return $value; } $this->clearErrors(); $field = $this->getFieldClass($fieldInfos->type); if (isset($field)) { $value = $field->getPostSearchFieldValue($fieldInfos, $key, $defaultValue, $keyPrefix, $keySuffix); } else { $value = $defaultValue; } return $value; } /** * Return HTML string to put an input search field into a page * * @param FieldInfos $fieldInfos Properties of the field * @param string $key Key of attribute * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) * @param string $moreCss Value for css to define style/length of field. * @param string $moreAttrib To add more attributes on html input tag * @param int<0,1> $noNewButton Force to not show the new button on field that are links to object * @return string */ public function printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '', $noNewButton = 0) { global $hookmanager; $overwrite_before = ''; $overwrite_content = ''; $overwrite_after = ''; $parameters = array( 'fieldInfos' => &$fieldInfos, 'key' => $key, 'value' => &$value, 'keyPrefix' => $keyPrefix, 'keySuffix' => $keySuffix, 'moreCss' => $moreCss, 'moreAttrib' => $moreAttrib, 'noNewButton' => $noNewButton, 'overwrite_before' => &$overwrite_before, 'overwrite_content' => &$overwrite_content, 'overwrite_after' => &$overwrite_after, ); $hookmanager->executeHooks('printInputSearchField', $parameters, $this); // Note that $this may have been modified by hook if (!empty($fieldInfos->computed)) { return ''; } $out = $overwrite_before; if (empty($overwrite_content)) { $this->clearErrors(); $field = $this->getFieldClass($fieldInfos->type); if (isset($field)) { $moreCss = $field->getInputCss($fieldInfos, $moreCss); $out .= $field->printInputSearchField($fieldInfos, $key, $value, $keyPrefix, $keySuffix, $moreCss, $moreAttrib); } else { $out .= $this->errorsToString(); } } else { $out .= $overwrite_content; } $out .= $overwrite_after; return $out; } /** * Return HTML string to put an input field into a page * * @param FieldInfos $fieldInfos Properties of the field * @param string $key Key of attribute * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) * @param string $moreCss Value for css to define style/length of field. * @param string $moreAttrib To add more attributes on html input tag * @param int<0,1> $noNewButton Force to not show the new button on field that are links to object * @return string */ public function printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '', $noNewButton = 0) { global $hookmanager, $langs; $overwrite_before = ''; $overwrite_content = ''; $overwrite_after = ''; $parameters = array( 'fieldInfos' => &$fieldInfos, 'key' => $key, 'value' => &$value, 'keyPrefix' => $keyPrefix, 'keySuffix' => $keySuffix, 'moreCss' => $moreCss, 'moreAttrib' => $moreAttrib, 'noNewButton' => $noNewButton, 'overwrite_before' => &$overwrite_before, 'overwrite_content' => &$overwrite_content, 'overwrite_after' => &$overwrite_after, ); $hookmanager->executeHooks('printInputField', $parameters, $this); // Note that $this may have been modified by hook if (!empty($fieldInfos->computed)) { return '' . $langs->trans("AutomaticallyCalculated") . ''; } if (!$fieldInfos->editable) { return $this->printOutputField($fieldInfos, $key, $value, $keyPrefix, $keySuffix, $moreCss, $moreAttrib); } // Get validation error $fieldValidationErrorMsg = $this->getFieldError($key); $out = $overwrite_before; if (empty($overwrite_content)) { $this->clearErrors(); $field = $this->getFieldClass($fieldInfos->type); if (isset($field)) { $moreCss = $field->getInputCss($fieldInfos, $moreCss); // Add validation state class if (!empty($fieldValidationErrorMsg)) { $moreCss .= ' --error'; // the -- is use as class state in css : .--error can't be be defined alone it must be define with another class like .my-class.--error or input.--error } else { $moreCss .= ' --success'; // the -- is use as class state in css : .--success can't be be defined alone it must be define with another class like .my-class.--success or input.--success } $out .= $field->printInputField($fieldInfos, $key, $value, $keyPrefix, $keySuffix, $moreCss, $moreAttrib); } else { $out .= $this->errorsToString(); } } else { $out .= $overwrite_content; } if (empty($overwrite_after)) { // Display error message for field $out .= $this->getFieldErrorIcon($fieldValidationErrorMsg); } else { $out .= $overwrite_after; } return $out; } /** * Return HTML string to show a field into a page * * @param FieldInfos $fieldInfos Properties of the field * @param string $key Key of attribute * @param mixed $value Preselected value to show (for date type it must be in timestamp format, for amount or price it must be a php numeric value, for array type must be array) * @param string $keyPrefix Prefix string to add into name and id of field (can be used to avoid duplicate names) * @param string $keySuffix Suffix string to add into name and id of field (can be used to avoid duplicate names) * @param string $moreCss Value for css to define style/length of field. * @param string $moreAttrib To add more attributes on html input tag * @return string */ public function printOutputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') { global $hookmanager; $overwrite_before = ''; $overwrite_content = ''; $overwrite_after = ''; $parameters = array( 'fieldInfos' => &$fieldInfos, 'key' => $key, 'value' => &$value, 'keyPrefix' => $keyPrefix, 'keySuffix' => $keySuffix, 'moreCss' => $moreCss, 'moreAttrib' => $moreAttrib, 'overwrite_before' => &$overwrite_before, 'overwrite_content' => &$overwrite_content, 'overwrite_after' => &$overwrite_after, ); $hookmanager->executeHooks('printOutputField', $parameters, $this); // Note that $object may have been modified by hook $out = $overwrite_before; if (empty($overwrite_content)) { $this->clearErrors(); $field = $this->getFieldClass($fieldInfos->type); if (isset($field)) { $moreCss = $field->getInputCss($fieldInfos, $moreCss); $out .= $field->printOutputField($fieldInfos, $key, $value, $keyPrefix, $keySuffix, $moreCss, $moreAttrib); } else { $out .= $this->errorsToString(); } } else { $out .= $overwrite_content; } $out .= $overwrite_after; return $out; } /** * Return HTML string to print separator field * * @param string $key Key of attribute * @param object $object Object * @param int $colspan Value of colspan to use (it must include the first column with title) * @param string $display_type "card" for form display, "line" for document line display * @param string $mode Show output ('view') or input ('create' or 'edit') for field * @return string HTML code with line for separator */ public function printSeparator($key, &$object, $colspan = 2, $display_type = 'card', $mode = 'view') { global $conf, $langs; // TODO to adapt for field and not extra fields only $out = ''; /*$tagtype = 'tr'; $tagtype_dyn = 'td'; if ($display_type == 'line') { $tagtype = 'div'; $tagtype_dyn = 'span'; $colspan = 0; } $extrafield_param = $this->attributes[$object->table_element]['param'][$key]; $extrafield_param_list = array(); if (!empty($extrafield_param) && is_array($extrafield_param)) { $extrafield_param_list = array_keys($extrafield_param['options']); } // Set $extrafield_collapse_display_value (do we have to collapse/expand the group after the separator) $extrafield_collapse_display_value = -1; $expand_display = false; if (is_array($extrafield_param_list) && count($extrafield_param_list) > 0) { $extrafield_collapse_display_value = intval($extrafield_param_list[0]); $expand_display = ((isset($_COOKIE['DOLUSER_COLLAPSE_' . $object->table_element . '_extrafields_' . $key]) || GETPOSTINT('ignorecollapsesetup')) ? (!empty($_COOKIE['DOLUSER_COLLAPSE_' . $object->table_element . '_extrafields_' . $key])) : !($extrafield_collapse_display_value == 2)); } $disabledcookiewrite = 0; if ($mode == 'create') { // On create mode, force separator group to not be collapsible $extrafield_collapse_display_value = 1; $expand_display = true; // We force group to be shown expanded $disabledcookiewrite = 1; // We keep status of group unchanged into the cookie } $out = '<' . $tagtype . ' id="trextrafieldseparator' . $key . (!empty($object->id) ? '_' . $object->id : '') . '" class="trextrafieldseparator trextrafieldseparator' . $key . (!empty($object->id) ? '_' . $object->id : '') . '">'; $out .= '<' . $tagtype_dyn . ' ' . (!empty($colspan) ? 'colspan="' . $colspan . '"' : '') . '>'; // Some js code will be injected here to manage the collapsing of fields // Output the picto $out .= ''; $out .= ' '; $out .= ''; $out .= $langs->trans($this->attributes[$object->table_element]['label'][$key]); $out .= ''; $out .= ''; $out .= ''; $collapse_group = $key . (!empty($object->id) ? '_' . $object->id : ''); //$extrafields_collapse_num = $this->attributes[$object->table_element]['pos'][$key].(!empty($object->id)?'_'.$object->id:''); if ($extrafield_collapse_display_value == 1 || $extrafield_collapse_display_value == 2) { // Set the collapse_display status to cookie in priority or if ignorecollapsesetup is 1, if cookie and ignorecollapsesetup not defined, use the setup. $this->expand_display[$collapse_group] = $expand_display; if (!empty($conf->use_javascript_ajax)) { $out .= '' . "\n"; $out .= '' . "\n"; } } else { $this->expand_display[$collapse_group] = 1; }*/ return $out; } }