From c46f09cac82ca18a3353835e90673cfc07c3146f Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 23 Jun 2025 14:11:02 +0200 Subject: [PATCH 001/387] feat: change token to checkbox for api on user --- htdocs/langs/en_US/admin.lang | 1 + htdocs/user/card.php | 26 +++++++++----------------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index 7288d77245f..d93e2c3cf51 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -1963,6 +1963,7 @@ ApiProductionMode=Enable production mode (this will activate use of a cache for ApiExporerIs=You can explore and test the APIs at URL OnlyActiveElementsAreExposed=Only elements from enabled modules are exposed ApiKey=Key for API +UseApiKey=Use keys for API WarningAPIExplorerDisabled=The API explorer has been disabled. API explorer is not required to provide API services. It is a tool for developer to find/test REST APIs. If you need this tool, go into setup of module API REST to activate it. ##### Bank ##### BankSetupModule=Bank module setup diff --git a/htdocs/user/card.php b/htdocs/user/card.php index daf327223a2..87d553b4d5f 100644 --- a/htdocs/user/card.php +++ b/htdocs/user/card.php @@ -468,7 +468,7 @@ if (empty($reshook)) { $object->pass = GETPOST("password", 'password'); } if ($permissiontoeditpasswordandsee || $user->hasRight("api", "apikey", "generate")) { - $object->api_key = (GETPOST("api_key", 'alphanohtml')) ? GETPOST("api_key", 'alphanohtml') : $object->api_key; + $object->api_key = GETPOST("api_key", 'alphanohtml'); } if (!empty($user->admin) && $user->id != $id) { // admin flag can only be set/unset by an admin user and not four ourself @@ -1237,12 +1237,9 @@ if ($action == 'create' || $action == 'adduserldap') { if (isModEnabled('api')) { // API key //$generated_password = getRandomPassword(false); - print ''.$langs->trans("ApiKey").''; + print ''.$langs->trans("UseApiKey").''; print ''; - print ''; - if (!empty($conf->use_javascript_ajax)) { - print img_picto($langs->transnoentities('Generate'), 'refresh', 'id="generate_api_key" class="linkobject paddingleft"'); - } + print ''; print ''; } else { // PARTIAL WORKAROUND @@ -2054,13 +2051,11 @@ if ($action == 'create' || $action == 'adduserldap') { // API key if (isModEnabled('api') && ($user->id == $id || $user->admin || $user->hasRight("api", "apikey", "generate"))) { - print ''.$langs->trans("ApiKey").''; + print ''.$langs->trans("UseApiKey").''; print ''; - if (!empty($object->api_key)) { - print ''; - print showValueWithClipboardCPButton($object->api_key, 1, $langs->transnoentities("Hidden")); // TODO Add an option to also reveal the hash, not only copy paste - print ''; - } + print ''; + print empty($object->api_key) ? $langs->trans("No") : $langs->trans("Yes"); + print ''; print ''; } if ((getDolGlobalInt('MAIN_ENABLE_LOGINS_PRIVACY') == 0) || (getDolGlobalInt('MAIN_ENABLE_LOGINS_PRIVACY') == 1 && $object->id == $user->id)) { @@ -2650,13 +2645,10 @@ if ($action == 'create' || $action == 'adduserldap') { // API key if (isModEnabled('api')) { - print ''.$langs->trans("ApiKey").''; + print ''.$langs->trans("UseApiKey").''; print ''; if ($permissiontoeditpasswordandsee || $user->hasRight("api", "apikey", "generate")) { - print ''; - if (!empty($conf->use_javascript_ajax)) { - print img_picto($langs->transnoentities('Generate'), 'refresh', 'id="generate_api_key" class="linkobject paddingleft"'); - } + print 'api_key != '' ? ' checked="checked"' : "").'>'; } print ''; } From 42fceb4d052ac8401120abeeeefb51d2fd0a2994 Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 23 Jun 2025 16:32:34 +0200 Subject: [PATCH 002/387] feat: token list tab working --- htdocs/core/lib/usergroups.lib.php | 7 + htdocs/langs/en_US/users.lang | 3 + htdocs/user/api_token/list.php | 237 +++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 htdocs/user/api_token/list.php diff --git a/htdocs/core/lib/usergroups.lib.php b/htdocs/core/lib/usergroups.lib.php index 3b6c29c94e6..1e698c743dd 100644 --- a/htdocs/core/lib/usergroups.lib.php +++ b/htdocs/core/lib/usergroups.lib.php @@ -226,6 +226,13 @@ function user_prepare_head(User $object) $h++; } + if (!empty($object->api_key)) { + $head[$h][0] = DOL_URL_ROOT.'/user/api_token/list.php?id='.$object->id; + $head[$h][1] = $langs->trans("ApiToken"); + $head[$h][2] = 'apitoken'; + $h++; + } + complete_head_from_modules($conf, $langs, $object, $head, $h, 'user', 'remove'); return $head; diff --git a/htdocs/langs/en_US/users.lang b/htdocs/langs/en_US/users.lang index b1505c79b80..12cb850fa6f 100644 --- a/htdocs/langs/en_US/users.lang +++ b/htdocs/langs/en_US/users.lang @@ -144,3 +144,6 @@ CloneCategoriesUser=Clone the user's categories ConfirmUserClone=Are you sure you want to clone the user: %s? NewEmailUserClone=Email address of the new user SocialNetworksUser=Social networks for user +ApiToken=Api token +ListOfTokensForUser=List of tokens for this user +NumberOfPermissions=Number of permissions diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php new file mode 100644 index 00000000000..756f0285d7a --- /dev/null +++ b/htdocs/user/api_token/list.php @@ -0,0 +1,237 @@ + + * Copyright (C) 2010-2015 Regis Houssin + * Copyright (C) 2013 Florian Henry + * Copyright (C) 2018 Ferran Marcet + * Copyright (C) 2024 Frédéric France + * Copyright (C) 2024-2025 MDW + * + * 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/user/param_ihm.php + * \brief Page to show user setup for display + */ + +// Load Dolibarr environment +require '../../main.inc.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/usergroups.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php'; + +/** + * @var Conf $conf + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + */ + +// Load translation files required by page +$langs->loadLangs(array('companies', 'products', 'admin', 'users', 'languages', 'projects', 'members')); + +// Defini si peux lire/modifier permissions +$canreaduser = ($user->admin || $user->hasRight("user", "user", "read")); +$caneditfield = false; + +$id = GETPOSTINT('id'); +$action = GETPOST('action', 'aZ09'); +$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'userihm'; // To manage different context of search + +if (!isset($id) || empty($id)) { + accessforbidden(); +} +'@phan-var-force int<1,max> $id'; + +// $user est le user qui edite, $id est l'id de l'utilisateur edite +$caneditfield = ((($user->id == $id) && $user->hasRight("user", "self", "write")) + || (($user->id != $id) && $user->hasRight("user", "user", "write"))); + + +// Security check +$socid = 0; +if ($user->socid > 0) { + $socid = $user->socid; +} +$feature2 = (($socid && $user->hasRight("user", "self", "write")) ? '' : 'user'); + +// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context +$hookmanager->initHooks(array('usercard', 'userihm', 'globalcard')); + +$result = restrictedArea($user, 'user', $id, 'user&user', $feature2); +if ($user->id != $id && !$canreaduser) { + accessforbidden(); +} + +$object = new User($db); +$object->fetch($id, '', '', 1); +$object->loadRights(); + +$form = new Form($db); +$formadmin = new FormAdmin($db); + +/* + * Actions + */ + +$parameters = array('id' => $socid); +$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks +if ($reshook < 0) { + setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); +} + +if (empty($reshook)) { + if ($action == 'update' && ($caneditfield || !empty($user->admin))) { + header('Location: '.$_SERVER["PHP_SELF"].'?id='.$id); + exit; + } +} + + +/* + * View + */ + +$person_name = !empty($object->firstname) ? $object->lastname.", ".$object->firstname : $object->lastname; +$title = $person_name." - ".$langs->trans('Card'); +$help_url = ''; + +llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-user page-card_param_ihm'); + +$head = user_prepare_head($object); + +$title = $langs->trans("User"); + +if ($action == 'edit') { + print '
'; + print ''; + print ''; + print ''; +} + +print dol_get_fiche_head($head, 'apitoken', $title, -1, 'user'); + +$linkback = ''.$langs->trans("BackToList").''; + +$morehtmlref = ''; +$morehtmlref .= img_picto($langs->trans("Download").' '.$langs->trans("VCard"), 'vcard.png', 'class="valignmiddle marginleftonly paddingrightonly"'); +$morehtmlref .= ''; + +$urltovirtualcard = '/user/virtualcard.php?id='.((int) $object->id); +$morehtmlref .= dolButtonToOpenUrlInDialogPopup('publicvirtualcard', $langs->transnoentitiesnoconv("PublicVirtualCardUrl").' - '.$object->getFullName($langs), img_picto($langs->trans("PublicVirtualCardUrl"), 'card', 'class="valignmiddle marginleftonly paddingrightonly"'), $urltovirtualcard, '', 'nohover'); + +dol_banner_tab($object, 'id', $linkback, $user->hasRight("user", "user", "read") || $user->admin, 'rowid', 'ref', $morehtmlref); + +print '
'; + +print '
'; + +print ''; + +// Login +print ''; +if (!empty($object->ldap_sid) && $object->status == 0) { + print ''; +} else { + print ''; +} +print ''."\n"; + +print '
'.$langs->trans("Login").''; + print $langs->trans("LoginAccountDisableInDolibarr"); + print ''; + $addadmin = ''; + if (property_exists($object, 'admin')) { + if (isModEnabled('multicompany') && !empty($object->admin) && empty($object->entity)) { + $addadmin .= img_picto($langs->trans("SuperAdministratorDesc"), "redstar", 'class="paddingleft valignmiddle"'); + } elseif (!empty($object->admin)) { + $addadmin .= img_picto($langs->trans("AdministratorDesc"), "star", 'class="paddingleft valignmiddle"'); + } + } + print showValueWithClipboardCPButton($object->login).$addadmin; + print '
'; + +print '
'; + +print dol_get_fiche_end(); + +print ''."\n"; + +$morehtmlright = ''; +//if (!empty($moreoptions['showhideaddbutton']) && $conf->use_javascript_ajax) { +$tmpurlforbutton = DOL_URL_ROOT.'/user/api_token/list.php?id='.$id.'&action=create'; +// TODO Permissions ? $morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton, '', $permtoeditline); +$morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton); +//} + +print load_fiche_titre($langs->trans("ListOfTokensForUser"), $morehtmlright, ''); + +// TODO : Build the hook management +// Other form for add user to group +//$parameters = array('caneditgroup' => $permissiontoeditgroup, 'groupslist' => $groupslist, 'exclude' => $exclude); +//$reshook = $hookmanager->executeHooks('formAddUserToGroup', $parameters, $object, $action); // Note that $action and $object may have been modified by hook +//print $hookmanager->resPrint; + +if (empty($reshook)) { + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + $sql = "SELECT ot.token, ot.entity, ot.state, ot.datec, ot.tms"; + $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; + $sql .= " WHERE ot.fk_user = ".((int) $object->id); + + $resql = $db->query($sql); + + // List of groups of user + if ($db->num_rows($resql) > 0) { + while ($obj = $db->fetch_object($resql)) { + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + } + } else { + print ''; + } + + print "
'.$langs->trans("ApiToken").''.$langs->trans("Entity").''.$langs->trans("NumberOfPermissions").''.$langs->trans("DateCreation").''.$langs->trans("DateModification").'
'; + print $obj->token; + print ''; + print $obj->entity; + print ''; + print $obj->state; + print ''; + print $obj->datec; + print ''; + print $obj->tms; + print '
'.$langs->trans("None").'
"; + print "
"; +} + +// End of page +llxFooter(); +$db->close(); From b770bd01b301edfa8988fc99dacc06a58c307b50 Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 24 Jun 2025 11:18:06 +0200 Subject: [PATCH 003/387] feat: added massaction option --- htdocs/user/api_token/list.php | 36 ++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index 756f0285d7a..20b744febdd 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -56,6 +56,8 @@ if (!isset($id) || empty($id)) { } '@phan-var-force int<1,max> $id'; +$toselect = GETPOST('toselect', 'array'); + // $user est le user qui edite, $id est l'id de l'utilisateur edite $caneditfield = ((($user->id == $id) && $user->hasRight("user", "self", "write")) || (($user->id != $id) && $user->hasRight("user", "user", "write"))); @@ -111,6 +113,9 @@ $help_url = ''; llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-user page-card_param_ihm'); +$massaction = GETPOST('massaction', 'alpha'); +$arrayofselected = is_array($toselect) ? $toselect : array(); + $head = user_prepare_head($object); $title = $langs->trans("User"); @@ -135,6 +140,9 @@ $morehtmlref .= dolButtonToOpenUrlInDialogPopup('publicvirtualcard', $langs->tra dol_banner_tab($object, 'id', $linkback, $user->hasRight("user", "user", "read") || $user->admin, 'rowid', 'ref', $morehtmlref); +$arrayofmassactions['predelete'] = img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete"); +$massactionbutton = $form->selectMassAction('', $arrayofmassactions); + print '
'; print '
'; @@ -177,7 +185,11 @@ $tmpurlforbutton = DOL_URL_ROOT.'/user/api_token/list.php?id='.$id.'&action=crea $morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton); //} -print load_fiche_titre($langs->trans("ListOfTokensForUser"), $morehtmlright, ''); +print ''."\n"; +print ''; + +print load_fiche_titre($langs->trans("ListOfTokensForUser"), $morehtmlright, '', 0, '', '', $massactionbutton); +print ''; // TODO : Build the hook management // Other form for add user to group @@ -189,7 +201,13 @@ if (empty($reshook)) { print ''; print ''; + print ''; + if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } print ''; print ''; print ''; @@ -197,7 +215,7 @@ if (empty($reshook)) { print ''; print ''; - $sql = "SELECT ot.token, ot.entity, ot.state, ot.datec, ot.tms"; + $sql = "SELECT ot.rowid as token_id, ot.token, ot.entity, ot.state, ot.datec, ot.tms"; $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; $sql .= " WHERE ot.fk_user = ".((int) $object->id); @@ -207,8 +225,22 @@ if (empty($reshook)) { if ($db->num_rows($resql) > 0) { while ($obj = $db->fetch_object($resql)) { print ''; + // Action column + if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } print ''; print ''; print ''; } else { // PARTIAL WORKAROUND @@ -2648,7 +2648,7 @@ if ($action == 'create' || $action == 'adduserldap') { print ''; print ''; } From fa016210de175cacb6a68face20738b7d1acef15 Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 24 Jun 2025 17:33:02 +0200 Subject: [PATCH 006/387] feat: add card of token in user with token permission edition --- htdocs/langs/en_US/users.lang | 1 + htdocs/user/api_token/card.php | 747 +++++++++++++++++++++++++++++++++ htdocs/user/api_token/list.php | 2 +- 3 files changed, 749 insertions(+), 1 deletion(-) create mode 100644 htdocs/user/api_token/card.php diff --git a/htdocs/langs/en_US/users.lang b/htdocs/langs/en_US/users.lang index 12cb850fa6f..19402459a7a 100644 --- a/htdocs/langs/en_US/users.lang +++ b/htdocs/langs/en_US/users.lang @@ -146,4 +146,5 @@ NewEmailUserClone=Email address of the new user SocialNetworksUser=Social networks for user ApiToken=Api token ListOfTokensForUser=List of tokens for this user +ListOfRightsForToken=List of rights for this token NumberOfPermissions=Number of permissions diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php new file mode 100644 index 00000000000..a7bc2bbf2a0 --- /dev/null +++ b/htdocs/user/api_token/card.php @@ -0,0 +1,747 @@ + + * Copyright (C) 2010-2015 Regis Houssin + * Copyright (C) 2013 Florian Henry + * Copyright (C) 2018 Ferran Marcet + * Copyright (C) 2024 Frédéric France + * Copyright (C) 2024-2025 MDW + * + * 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/user/param_ihm.php + * \brief Page to show user setup for display + */ + +// Load Dolibarr environment +require '../../main.inc.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/usergroups.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php'; + +/** + * @var Conf $conf + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + */ + +// Load translation files required by page +$langs->loadLangs(array('admin', 'users')); + +// Security check +$id = GETPOSTINT('id'); +$action = GETPOST('action', 'aZ09'); +$tokenid = GETPOST('tokenid', 'aZ09'); +$massaction = GETPOST('massaction', 'alpha'); + +if (!isset($id) || empty($id)) { + accessforbidden(); +} + +// Retrieve needed GETPOSTS for this file +$toselect = GETPOST('toselect', 'array'); + +// $user is current user, $id is id of edited user +$canreaduser = ($user->admin || $user->hasRight("user", "user", "read")); +$caneditfield = ((($user->id == $id) && $user->hasRight("user", "self", "write")) + || (($user->id != $id) && $user->hasRight("user", "user", "write"))); +$caneditperms = ($user->admin || $user->hasRight("user", "user", "write")); + +// Security check +$socid = 0; +if ($user->socid > 0) { + $socid = $user->socid; +} +$feature2 = (($socid && $user->hasRight("user", "self", "write")) ? '' : 'user'); + +$result = restrictedArea($user, 'user', $id, 'user&user', $feature2); +if ($user->id != $id && !$canreaduser) { + accessforbidden(); +} + +$sql = "SELECT ot.rowid as token_id, ot.token, ot.entity, ot.state, ot.datec, ot.tms"; +$sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; +$sql .= " WHERE ot.rowid = ".((int) $tokenid); + +$resql = $db->query($sql); + +$object = new User($db); +$object->fetch($id, '', '', 1); +$object->loadRights(); + +$form = new Form($db); +$formadmin = new FormAdmin($db); +$token = $db->fetch_object($resql); + +/* + * Actions + */ + +$parameters = array('id' => $socid); +$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks +if ($reshook < 0) { + setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); +} + +if (empty($reshook)) { + if ($action == 'update' && ($caneditfield || !empty($user->admin))) { + header('Location: '.$_SERVER["PHP_SELF"].'?id='.$id); + exit; + } +} + + +/* + * View + */ + +$person_name = !empty($object->firstname) ? $object->lastname.", ".$object->firstname : $object->lastname; +$title = $person_name." - ".$langs->trans('Card'); +$help_url = ''; + +llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-user page-card_param_ihm'); + +$arrayofselected = is_array($toselect) ? $toselect : array(); + +$head = user_prepare_head($object); + +$title = $langs->trans("User"); + +print dol_get_fiche_head($head, 'apitoken', $title, -1, 'user'); + +$linkback = ''.$langs->trans("BackToList").''; + +$morehtmlref = ''; +$morehtmlref .= img_picto($langs->trans("Download").' '.$langs->trans("VCard"), 'vcard.png', 'class="valignmiddle marginleftonly paddingrightonly"'); +$morehtmlref .= ''; + +$urltovirtualcard = '/user/virtualcard.php?id='.((int) $object->id); +$morehtmlref .= dolButtonToOpenUrlInDialogPopup('publicvirtualcard', $langs->transnoentitiesnoconv("PublicVirtualCardUrl").' - '.$object->getFullName($langs), img_picto($langs->trans("PublicVirtualCardUrl"), 'card', 'class="valignmiddle marginleftonly paddingrightonly"'), $urltovirtualcard, '', 'nohover'); + +dol_banner_tab($object, 'id', $linkback, $user->hasRight("user", "user", "read") || $user->admin, 'rowid', 'ref', $morehtmlref); + +print '
'."\n"; +print ''; +print ''; +print ''; + +print '
'; +print '
'; +print '
'; + print $form->showCheckAddButtons('checkforselect', 1); + print ''.$langs->trans("ApiToken").''.$langs->trans("Entity").''.$langs->trans("NumberOfPermissions").''.$langs->trans("DateModification").'
'; + if ($massactionbutton || $massaction) { // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined + $selected = 0; + if (in_array($obj->token_id, $arrayofselected)) { + $selected = 1; + } + print ''; + } + print ''; + print ''.img_object($langs->trans("ShowToken"), "email").' '; // TODO : change icon print $obj->token; + print ''; print ''; print $obj->entity; From 336a4c57e08acbaca8a12d367617cfacd8ea7c44 Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 24 Jun 2025 11:40:42 +0200 Subject: [PATCH 004/387] refactor: clean code --- htdocs/user/api_token/list.php | 37 +++++++++------------------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index 20b744febdd..4389a93ef60 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -41,28 +41,25 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php'; */ // Load translation files required by page -$langs->loadLangs(array('companies', 'products', 'admin', 'users', 'languages', 'projects', 'members')); - -// Defini si peux lire/modifier permissions -$canreaduser = ($user->admin || $user->hasRight("user", "user", "read")); -$caneditfield = false; +$langs->loadLangs(array('admin', 'users')); +// Security check $id = GETPOSTINT('id'); $action = GETPOST('action', 'aZ09'); -$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'userihm'; // To manage different context of search +$massaction = GETPOST('massaction', 'alpha'); if (!isset($id) || empty($id)) { accessforbidden(); } -'@phan-var-force int<1,max> $id'; +// Retrieve needed GETPOSTS for this file $toselect = GETPOST('toselect', 'array'); -// $user est le user qui edite, $id est l'id de l'utilisateur edite +// $user is current user, $id is id of edited user +$canreaduser = ($user->admin || $user->hasRight("user", "user", "read")); $caneditfield = ((($user->id == $id) && $user->hasRight("user", "self", "write")) || (($user->id != $id) && $user->hasRight("user", "user", "write"))); - // Security check $socid = 0; if ($user->socid > 0) { @@ -70,9 +67,6 @@ if ($user->socid > 0) { } $feature2 = (($socid && $user->hasRight("user", "self", "write")) ? '' : 'user'); -// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context -$hookmanager->initHooks(array('usercard', 'userihm', 'globalcard')); - $result = restrictedArea($user, 'user', $id, 'user&user', $feature2); if ($user->id != $id && !$canreaduser) { accessforbidden(); @@ -113,20 +107,12 @@ $help_url = ''; llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-user page-card_param_ihm'); -$massaction = GETPOST('massaction', 'alpha'); $arrayofselected = is_array($toselect) ? $toselect : array(); $head = user_prepare_head($object); $title = $langs->trans("User"); -if ($action == 'edit') { - print '
'; - print ''; - print ''; - print ''; -} - print dol_get_fiche_head($head, 'apitoken', $title, -1, 'user'); $linkback = ''.$langs->trans("BackToList").''; @@ -140,13 +126,8 @@ $morehtmlref .= dolButtonToOpenUrlInDialogPopup('publicvirtualcard', $langs->tra dol_banner_tab($object, 'id', $linkback, $user->hasRight("user", "user", "read") || $user->admin, 'rowid', 'ref', $morehtmlref); -$arrayofmassactions['predelete'] = img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete"); -$massactionbutton = $form->selectMassAction('', $arrayofmassactions); - print '
'; - print '
'; - print ''; // Login @@ -169,15 +150,16 @@ if (!empty($object->ldap_sid) && $object->status == 0) { print ''; } print ''."\n"; - print '
'; - print '
'; print dol_get_fiche_end(); print ''."\n"; +$arrayofmassactions['predelete'] = img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete"); +$massactionbutton = $form->selectMassAction('', $arrayofmassactions); + $morehtmlright = ''; //if (!empty($moreoptions['showhideaddbutton']) && $conf->use_javascript_ajax) { $tmpurlforbutton = DOL_URL_ROOT.'/user/api_token/list.php?id='.$id.'&action=create'; @@ -187,7 +169,6 @@ $morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle print ''."\n"; print ''; - print load_fiche_titre($langs->trans("ListOfTokensForUser"), $morehtmlright, '', 0, '', '', $massactionbutton); print '
'; From a6e9a9ac1488c8ad4ba6055239c8ad8c80085456 Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 24 Jun 2025 16:38:12 +0200 Subject: [PATCH 005/387] fix: unique key violated when enabling api keys for user --- htdocs/user/card.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/user/card.php b/htdocs/user/card.php index 87d553b4d5f..b5fc6255bdd 100644 --- a/htdocs/user/card.php +++ b/htdocs/user/card.php @@ -1239,7 +1239,7 @@ if ($action == 'create' || $action == 'adduserldap') { //$generated_password = getRandomPassword(false); print '
'.$langs->trans("UseApiKey").''; - print ''; + print ''; print '
'.$langs->trans("UseApiKey").''; if ($permissiontoeditpasswordandsee || $user->hasRight("api", "apikey", "generate")) { - print 'api_key != '' ? ' checked="checked"' : "").'>'; + print 'api_key != '' ? ' checked="checked"' : "").'>'; } print '
'; + +// Login +print ''; +if (!empty($object->ldap_sid) && $object->status == 0) { + print ''; +} else { + print ''; +} +print ''."\n"; + +//var_dump(showValueWithClipboardCPButton($token->token, 1, $token->token)); + +// Token +print ''; +print ''; +print ''."\n"; + +// Entity +print ''; +print ''; +print ''."\n"; + +// Creation date +print ''; +print ''; +print ''."\n"; + +// Modification date +print ''; +print ''; +print ''."\n"; + +print '
'.$langs->trans("Login").''; + print $langs->trans("LoginAccountDisableInDolibarr"); + print ''; + $addadmin = ''; + if (property_exists($object, 'admin')) { + if (isModEnabled('multicompany') && !empty($object->admin) && empty($object->entity)) { + $addadmin .= img_picto($langs->trans("SuperAdministratorDesc"), "redstar", 'class="paddingleft valignmiddle"'); + } elseif (!empty($object->admin)) { + $addadmin .= img_picto($langs->trans("AdministratorDesc"), "star", 'class="paddingleft valignmiddle"'); + } + } + print showValueWithClipboardCPButton($object->login).$addadmin; + print '
'.$langs->trans("ApiToken").''; +print ''; +if (!empty($conf->use_javascript_ajax)) { + print img_picto($langs->transnoentities('Generate'), 'refresh', 'id="generate_api_key" class="linkobject paddingleft"'); +} +print '
'.$langs->trans("Entity").''; +print ''; +print '
'.$langs->trans("DateCreation").''; +print ''; +print '
'.$langs->trans("DateModification").''; +print ''; +print '
'; +print '
'; +print ''; +print ''.$langs->trans("Delete").''; +print '
'; +print '
'; +print ''; + +print dol_get_fiche_end(); + +print load_fiche_titre($langs->trans("ListOfRightsForToken"), '', ''); + + + + + // TODO : Rights part +print ''."\n"; + +if ($user->admin) { + print info_admin($langs->trans("WarningOnlyPermissionOfActivatedModules")); +} + +$db->begin(); + +// Search all modules with permission and reload permissions def. +$modules = array(); +$modulesdir = dolGetModulesDirs(); + +foreach ($modulesdir as $dir) { + $handle = @opendir(dol_osencode($dir)); + if (is_resource($handle)) { + while (($file = readdir($handle)) !== false) { + if (is_readable($dir.$file) && substr($file, 0, 3) == 'mod' && substr($file, dol_strlen($file) - 10) == '.class.php') { + $modName = substr($file, 0, dol_strlen($file) - 10); + + if ($modName) { + include_once $dir.$file; + $objMod = new $modName($db); + '@phan-var-force DolibarrModules $objMod'; + + // Load all lang files of module + if (isset($objMod->langfiles) && is_array($objMod->langfiles)) { + foreach ($objMod->langfiles as $domain) { + $langs->load($domain); + } + } + // Load all permissions + if ($objMod->rights_class) { + $ret = $objMod->insert_permissions(0, $token->entity); + $modules[$objMod->rights_class] = $objMod; + //print "modules[".$objMod->rights_class."]=$objMod;"; + } + } + } + } + } +} + +$db->commit(); + +// Load perms ids for the user +$permsuser = array(); + +$sql = "SELECT ot.state"; +$sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; +$sql .= " WHERE ot.rowid = ".((int) $token->token_id); + +dol_syslog("get user perms", LOG_DEBUG); +$result = $db->query($sql); + +if ($result) { + $obj = $db->fetch_object($result); + $permsuser = explode(",", $obj->state); + $db->free($result); +} else { + dol_print_error($db); +} + +print '
'; +print ''; + +print ''; +print ''; +if ($caneditperms) { + print ''; +} else { + print ''; +} +print ''; +//print ''; +print ''; +print ''."\n"; + +// Load modules rights and correct position if needed +$sql = "SELECT r.id, r.libelle as label, r.module, r.perms, r.subperms, r.module_position, r.bydefault"; +$sql .= " FROM ".MAIN_DB_PREFIX."rights_def as r"; +$sql .= " WHERE r.libelle NOT LIKE 'tou%'"; // On ignore droits "tous" +$sql .= " AND r.entity = ".((int) $entity); +$sql .= " ORDER BY r.family_position, r.module_position, r.module, r.id"; + +$result = $db->query($sql); +if ($result) { + $num = $db->num_rows($result); + $i = 0; + $oldmod = ''; + + while ($i < $num) { + $obj = $db->fetch_object($result); + + // If line is for a module that does not exist anymore (absent of includes/module), we ignore it + if (!isset($obj->module) || empty($modules[$obj->module])) { + $i++; + continue; + } + + // Special cases + if (isModEnabled("reception")) { + // The 2 permissions in fournisseur modules are replaced by permissions into reception module + if ($obj->module == 'fournisseur' && $obj->perms == 'commande' && $obj->subperms == 'receptionner') { + $i++; + continue; + } + if ($obj->module == 'fournisseur' && $obj->perms == 'commande_advance' && $obj->subperms == 'check') { + $i++; + continue; + } + } + + $objMod = $modules[$obj->module]; + + // Save field module_position in database if value is wrong + if (empty($obj->module_position) || (is_object($objMod) && $objMod->isCoreOrExternalModule() == 'external' && $obj->module_position < 100000)) { + if (is_object($modules[$obj->module]) && ($modules[$obj->module]->module_position > 0)) { + // TODO Define familyposition + //$familyposition = $modules[$obj->module]->family_position; + $familyposition = 0; + + $newmoduleposition = $modules[$obj->module]->module_position; + + // Correct $newmoduleposition position for external modules + $objMod = $modules[$obj->module]; + if (is_object($objMod) && $objMod->isCoreOrExternalModule() == 'external' && $newmoduleposition < 100000) { + $newmoduleposition += 100000; + } + + $sqlupdate = 'UPDATE '.MAIN_DB_PREFIX."rights_def SET module_position = ".((int) $newmoduleposition).","; + $sqlupdate .= " family_position = ".((int) $familyposition); + $sqlupdate .= " WHERE module_position = ".((int) $obj->module_position)." AND module = '".$db->escape($obj->module)."'"; + + $db->query($sqlupdate); + } + } + } +} + +// Load and show all the perms grouped by module + +//print "xx".$conf->global->MAIN_USE_ADVANCED_PERMS; +$sql = "SELECT r.id, r.libelle as label, r.module, r.perms, r.subperms, r.module_position, r.bydefault"; +$sql .= " FROM ".MAIN_DB_PREFIX."rights_def as r"; +$sql .= " WHERE r.libelle NOT LIKE 'tou%'"; // On ignore droits "tous" +$sql .= " AND r.entity = ".((int) $entity); +if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { + $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled +} +$sql .= " ORDER BY r.family_position, r.module_position, r.module, r.id"; + +$result = $db->query($sql); +if ($result) { + $num = $db->num_rows($result); + $i = 0; + $j = 0; + $oldmod = ''; + + $cookietohidegroup = (empty($_COOKIE["DOLUSER_PERMS_HIDE_GRP"]) ? '' : preg_replace('/^,/', '', $_COOKIE["DOLUSER_PERMS_HIDE_GRP"])); + $cookietohidegrouparray = explode(',', $cookietohidegroup); + //var_dump($cookietohidegrouparray); + + while ($i < $num) { + $obj = $db->fetch_object($result); + + // If line is for a module that does not exist anymore (absent of includes/module), we ignore it + if (empty($modules[$obj->module])) { + $i++; + continue; + } + + // Special cases + if (isModEnabled("reception")) { + // The 2 permission in fournisseur modules has been replaced by permissions into reception module + if ($obj->module == 'fournisseur' && $obj->perms == 'commande' && $obj->subperms == 'receptionner') { + $i++; + continue; + } + if ($obj->module == 'fournisseur' && $obj->perms == 'commande_advance' && $obj->subperms == 'check') { + $i++; + continue; + } + } + + $objMod = $modules[$obj->module]; + + if (GETPOSTISSET('forbreakperms_'.$obj->module)) { + $ishidden = GETPOSTINT('forbreakperms_'.$obj->module); + } elseif (in_array($j, $cookietohidegrouparray)) { // If j is among list of hidden group + $ishidden = 1; + } else { + $ishidden = 0; + } + $isexpanded = ! $ishidden; + //var_dump("isexpanded=".$isexpanded); + + $permsgroupbyentitypluszero = array(); + if (!empty($permsgroupbyentity[0])) { + $permsgroupbyentitypluszero = array_merge($permsgroupbyentitypluszero, $permsgroupbyentity[0]); + } + if (!empty($permsgroupbyentity[$entity])) { + $permsgroupbyentitypluszero = array_merge($permsgroupbyentitypluszero, $permsgroupbyentity[$entity]); + } + //var_dump($permsgroupbyentitypluszero); + + // Break found, it's a new module to catch + if (isset($obj->module) && ($oldmod != $obj->module)) { + $oldmod = $obj->module; + + $j++; + if (GETPOSTISSET('forbreakperms_'.$obj->module)) { + $ishidden = GETPOSTINT('forbreakperms_'.$obj->module); + } elseif (in_array($j, $cookietohidegrouparray)) { // If j is among list of hidden group + $ishidden = 1; + } else { + $ishidden = 0; + } + $isexpanded = ! $ishidden; + //var_dump('$obj->module='.$obj->module.' isexpanded='.$isexpanded); + + // Break detected, we get objMod + $objMod = $modules[$obj->module]; + $picto = ($objMod->picto ? $objMod->picto : 'generic'); + + // Show break line + print ''; + // Picto and label of module + print ''; + + // Permission and tick (2 columns) + if (($caneditperms && empty($objMod->rights_admin_allowed)) || empty($object->admin)) { + if ($caneditperms) { + print ''; + print ''; + } else { + print ''; + print ''; + } + } else { + if ($caneditperms) { + print ''; + print ''; + } else { + print ''; + print ''; + } + } + + // Description of permission (2 columns) + print ''; + print ''; //Add picto + / - when open en closed + print ''."\n"; + } + + $permlabel = (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && ($langs->trans("PermissionAdvanced".$obj->id) != "PermissionAdvanced".$obj->id) ? $langs->trans("PermissionAdvanced".$obj->id) : (($langs->trans("Permission".$obj->id) != "Permission".$obj->id) ? $langs->trans("Permission".$obj->id) : $langs->trans($obj->label))); + + print ''."\n"; + print ''; + + // Picto and label of module + print ''; + + // Permission and tick (2 columns) + if (!empty($object->admin) && !empty($objMod->rights_admin_allowed)) { // Permission granted because admin + print ''; + if ($caneditperms) { + print ''; + } else { + print ''; + } + print ''; + } elseif (in_array($obj->id, $permsuser)) { // Permission granted by user + print ''; + if ($caneditperms) { + print ''; + } else { + print ''; + } + print ''; + } elseif (isset($permsgroupbyentitypluszero) && is_array($permsgroupbyentitypluszero)) { + print ''; + if (in_array($obj->id, $permsgroupbyentitypluszero)) { // Permission granted by group + print ''; + print ''; + } else { + // Do not own permission + if ($caneditperms) { + print ''; + } else { + print ''; + } + print ''; + } + } else { + // Do not own permission + print ''; + if ($caneditperms) { + print ''; + } else { + print ''; + } + print ''; + } + + // Description of permission (1 or 2 columns) + if (!$user->admin) { + print ''; + + // Permission id + if ($user->admin) { + print ''; + } + + print ''."\n"; + + $i++; + } +} else { + dol_print_error($db); +} +print '
'.$langs->trans("Module").''; + print ''.$langs->trans("All").""; + print ' / '; + print ''.$langs->trans("None").""; + print ''; +print ''.img_picto('', 'folder-open', 'class="paddingright"').''.$langs->trans("ExpandAll").''; +print ' | '; +print ''.img_picto('', 'folder', 'class="paddingright"').''.$langs->trans("UndoExpandAll").''; +print '
'; + print ''; + print img_object('', $picto, 'class="pictoobjectwidth paddingright"').' '.$objMod->getName(); + print ''; + print ''; + print ''; + print ''; + print ''; + /*print 'module.'&confirm=yes&updatedmodulename='.$obj->module.'">'.$langs->trans("All").""; + print ' / '; + print 'module.'&confirm=yes&updatedmodulename='.$obj->module.'">'.$langs->trans("None").""; + */ + print ''; + print ''; + + print ''; + print ''; + + print '
'; +print '
'; + +print ''; + +print ''; + +// TODO : Build the hook management +// Other form for add user to group +//$parameters = array('caneditgroup' => $permissiontoeditgroup, 'groupslist' => $groupslist, 'exclude' => $exclude); +//$reshook = $hookmanager->executeHooks('formAddUserToGroup', $parameters, $object, $action); // Note that $action and $object may have been modified by hook +//print $hookmanager->resPrint; + +include_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php'; +print dolJSToSetRandomPassword('api_key', 'generate_api_key', 1); + +// End of page +llxFooter(); +$db->close(); diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index 4389a93ef60..d5a3895b27e 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -219,7 +219,7 @@ if (empty($reshook)) { print ''; } print ''; - print ''.img_object($langs->trans("ShowToken"), "email").' '; // TODO : change icon + print ''.img_object($langs->trans("ShowToken"), "email").' '; // TODO : change icon print $obj->token; print ''; print ''; From 5404bd6b166f6a335d99d3bd17f6209c7ee151e9 Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 25 Jun 2025 11:12:38 +0200 Subject: [PATCH 007/387] feat: change var names for understandability --- htdocs/user/api_token/card.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index a7bc2bbf2a0..05f891468a2 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -74,7 +74,7 @@ if ($user->id != $id && !$canreaduser) { accessforbidden(); } -$sql = "SELECT ot.rowid as token_id, ot.token, ot.entity, ot.state, ot.datec, ot.tms"; +$sql = "SELECT ot.rowid as token_id, ot.token, ot.entity, ot.state as rights, ot.datec as date_creation, ot.tms as date_modification"; $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; $sql .= " WHERE ot.rowid = ".((int) $tokenid); @@ -187,14 +187,14 @@ print ''."\n"; // Creation date print ''.$langs->trans("DateCreation").''; print ''; -print ''; +print ''; print ''; print ''."\n"; // Modification date print ''.$langs->trans("DateModification").''; print ''; -print ''; +print ''; print ''; print ''."\n"; From 94bf6ed9fa213f74712f8379970772609985b79c Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 25 Jun 2025 11:13:16 +0200 Subject: [PATCH 008/387] feat: add delete rights working --- htdocs/user/api_token/card.php | 94 ++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 28 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 05f891468a2..86082b4c9bd 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -47,7 +47,9 @@ $langs->loadLangs(array('admin', 'users')); $id = GETPOSTINT('id'); $action = GETPOST('action', 'aZ09'); $tokenid = GETPOST('tokenid', 'aZ09'); -$massaction = GETPOST('massaction', 'alpha'); +$confirm = GETPOST('confirm', 'alpha'); +$module = GETPOST('module', 'alpha'); +$rights = GETPOSTINT('rights'); if (!isset($id) || empty($id)) { accessforbidden(); @@ -99,9 +101,63 @@ if ($reshook < 0) { } if (empty($reshook)) { - if ($action == 'update' && ($caneditfield || !empty($user->admin))) { - header('Location: '.$_SERVER["PHP_SELF"].'?id='.$id); - exit; + if (in_array($action, array('addrights', 'delrights')) && $module != '') { + $rigthsarray = []; + $sql = "SELECT id"; + $sql .= " FROM ".MAIN_DB_PREFIX."rights_def"; + $sql .= " WHERE entity IN (".$db->sanitize($token->entity, 0, 0, 0, 0).")"; + if ($module != 'allmodules') { + $sql .= " AND (module='".$db->escape($module)."')"; // Note: parenthesis are important because wherefordel can contains OR. Also note that $wherefordel is already sanitized + } + $resql = $db->query($sql); + while ($obj = $db->fetch_object($resql)) { + $rigthsarray []= $obj->id; + } + } + if ($action == 'addrights' && $caneditperms && $confirm == 'yes') { + $tokenrigthsarray = explode(',', $token->rights); + if (isset($rigthsarray)) { + $tokenrigthsarray = array_merge($tokenrigthsarray, $rigthsarray); + } else { + $tokenrigthsarray []= $rights; + } + + sort($tokenrigthsarray); + $newrigths = preg_replace('/\s+/', '', implode(',', $tokenrigthsarray)); + + $sql = "UPDATE ".MAIN_DB_PREFIX."oauth_token"; + $sql.= " SET state = '".$db->escape($newrigths)."'"; + $sql.= " WHERE rowid = '".$tokenid."'"; + + $resql = $db->query($sql); + if (!$resql) { + dol_print_error($db); + } + + $token->rights = $newrigths; + } + + if ($action == 'delrights' && $caneditperms && $confirm == 'yes') { + $tokenrigthsarray = explode(',', $token->rights); + if (isset($rigthsarray)) { + $tokenrigthsarray = array_diff($tokenrigthsarray, $rigthsarray); + } else { + $tokenrigthsarray = array_diff($tokenrigthsarray, array($rights)); + } + + sort($tokenrigthsarray); + $newrigths = preg_replace('/\s+/', '', implode(',', $tokenrigthsarray)); + + $sql = "UPDATE ".MAIN_DB_PREFIX."oauth_token"; + $sql.= " SET state = '".$db->escape($newrigths)."'"; + $sql.= " WHERE rowid = '".$tokenid."'"; + + $resql = $db->query($sql); + if (!$resql) { + dol_print_error($db); + } + + $token->rights = $newrigths; } } @@ -259,22 +315,8 @@ foreach ($modulesdir as $dir) { $db->commit(); // Load perms ids for the user -$permsuser = array(); - -$sql = "SELECT ot.state"; -$sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; -$sql .= " WHERE ot.rowid = ".((int) $token->token_id); - +$permsuser = explode(",", $token->rights); dol_syslog("get user perms", LOG_DEBUG); -$result = $db->query($sql); - -if ($result) { - $obj = $db->fetch_object($result); - $permsuser = explode(",", $obj->state); - $db->free($result); -} else { - dol_print_error($db); -} print '
'; print ''; @@ -460,9 +502,9 @@ if ($result) { if ($caneditperms) { print ''; print ''; print ''; @@ -529,7 +567,7 @@ if ($result) { print ''; if ($caneditperms) { print '
'; print ''; print ''; @@ -474,10 +516,6 @@ if ($result) { } else { if ($caneditperms) { print ''; - /*print 'module.'&confirm=yes&updatedmodulename='.$obj->module.'">'.$langs->trans("All").""; - print ' / '; - print 'module.'&confirm=yes&updatedmodulename='.$obj->module.'">'.$langs->trans("None").""; - */ print ''; print ''; - print 'id.'&confirm=yes&updatedmodulename='.$obj->module.'">'; + print 'id.'&confirm=yes">'; //print img_edit_remove($langs->trans("Remove")); print img_picto($langs->trans("Remove"), 'switch_on'); print ''; @@ -555,7 +593,7 @@ if ($result) { // Do not own permission if ($caneditperms) { print ''; - print 'id.'&confirm=yes&token='.newToken().'&updatedmodulename='.$obj->module.'">'; + print 'id.'&confirm=yes&token='.newToken().'">'; //print img_edit_add($langs->trans("Add")); print img_picto($langs->trans("Add"), 'switch_off'); print ''; @@ -573,7 +611,7 @@ if ($result) { print ''; if ($caneditperms) { print ''; - print 'id.'&confirm=yes&token='.newToken().'&updatedmodulename='.$obj->module.'">'; + print 'id.'&confirm=yes&token='.newToken().'">'; //print img_edit_add($langs->trans("Add")); print img_picto($langs->trans("Add"), 'switch_off'); print ''; From 931121dc22b2c5c0d99b809c867d3577cac0b32c Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 25 Jun 2025 12:18:45 +0200 Subject: [PATCH 009/387] feat: only show user perms in token perms settings --- htdocs/user/api_token/card.php | 39 +++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 86082b4c9bd..8fd61cd5fea 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -315,8 +315,7 @@ foreach ($modulesdir as $dir) { $db->commit(); // Load perms ids for the user -$permsuser = explode(",", $token->rights); -dol_syslog("get user perms", LOG_DEBUG); +$tokenperms = explode(",", $token->rights); print '
'; print ''; @@ -403,6 +402,35 @@ if ($result) { } } +//Load perms ids for user and user's groups to show only the perms the user has and avoid better perms in the token. +$allusersperms = array(); + +// Users perms +$sql = "SELECT ur.fk_id"; +$sql .= " FROM ".MAIN_DB_PREFIX."user_rights as ur"; +$sql .= " WHERE ur.entity = ".((int) $entity); +$sql .= " AND ur.fk_user = ".((int) $object->id); +$sql .= " UNION "; +// Groups perms +$sql .= "SELECT gr.fk_id"; +$sql .= " FROM ".MAIN_DB_PREFIX."usergroup_rights as gr"; +$sql .= " WHERE EXISTS(SELECT gu.rowid FROM llx_usergroup_user as gu WHERE gu.fk_user = ".((int) $id)." AND gu.fk_usergroup = gr.fk_usergroup)"; + +dol_syslog("get user perms", LOG_DEBUG); +$result = $db->query($sql); +if ($result) { + $num = $db->num_rows($result); + $i = 0; + while ($i < $num) { + $obj = $db->fetch_object($result); + array_push($allusersperms, $obj->fk_id); + $i++; + } + $db->free($result); +} else { + dol_print_error($db); +} + // Load and show all the perms grouped by module //print "xx".$conf->global->MAIN_USE_ADVANCED_PERMS; @@ -410,6 +438,7 @@ $sql = "SELECT r.id, r.libelle as label, r.module, r.perms, r.subperms, r.module $sql .= " FROM ".MAIN_DB_PREFIX."rights_def as r"; $sql .= " WHERE r.libelle NOT LIKE 'tou%'"; // On ignore droits "tous" $sql .= " AND r.entity = ".((int) $entity); +$sql .= " AND r.id IN (".implode(', ', $allusersperms).")"; if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled } @@ -563,7 +592,7 @@ if ($result) { } print ''; - } elseif (in_array($obj->id, $permsuser)) { // Permission granted by user + } elseif (in_array($obj->id, $tokenperms)) { // Permission granted by user print ''; if ($caneditperms) { print ''; print ''; if ($caneditperms) { print ''; } else { print ''; From d157ebc27b3d3f5db7a0c618219dae05c60e2e02 Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 25 Jun 2025 14:42:42 +0200 Subject: [PATCH 011/387] feat: auto add/delete perm if needed lower disabled --- htdocs/user/api_token/card.php | 65 ++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 2b044e31bcb..dcf3664332b 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -101,17 +101,62 @@ if ($reshook < 0) { } if (empty($reshook)) { - if (in_array($action, array('addrights', 'delrights')) && $module != '') { + if (in_array($action, array('addrights', 'delrights'))) { $rigthsarray = []; - $sql = "SELECT id"; - $sql .= " FROM ".MAIN_DB_PREFIX."rights_def"; - $sql .= " WHERE entity IN (".$db->sanitize($token->entity, 0, 0, 0, 0).")"; - if ($module != 'allmodules') { - $sql .= " AND (module='".$db->escape($module)."')"; // Note: parenthesis are important because wherefordel can contains OR. Also note that $wherefordel is already sanitized - } - $resql = $db->query($sql); - while ($obj = $db->fetch_object($resql)) { - $rigthsarray []= $obj->id; + + if ($rights != '') { + $sql = "SELECT module, perms, subperms"; + $sql .= " FROM ".$db->prefix()."rights_def"; + $sql .= " WHERE id = ".((int) $rights); + $sql .= " AND entity = ".((int) $token->entity); + + $result = $db->query($sql); + if ($result) { + $obj = $db->fetch_object($result); + + if ($obj) { + $module = $obj->module; + $perms = $obj->perms; + $subperms = $obj->subperms; + } + } else { + dol_print_error($db); + } + + $sql = "SELECT id"; + $sql .= " FROM ".$db->prefix()."rights_def"; + $sql .= " WHERE entity = ".((int) $token->entity); + $sql .= " AND (id=".((int) $rights); + if ($action == 'addrights') { + if (!empty($subperms)) { + $sql .= " OR (module='".$db->escape($module)."' AND perms='".$db->escape($perms)."' AND (subperms='lire' OR subperms='read'))"; + } elseif (!empty($perms)) { + $sql .= " OR (module='".$db->escape($module)."' AND (perms='lire' OR perms='read') AND (subperms IS NULL or subperms = ''))"; + } + } elseif ($action == 'delrights') { + if ($subperms == 'lire' || $subperms == 'read') { + $sql .= " OR (module='".$db->escape($module)."' AND perms='".$db->escape($perms)."' AND subperms IS NOT NULL)"; + } + if ($perms == 'lire' || $perms == 'read') { + $sql .= " OR (module='".$db->escape($module)."')"; + } + } + $sql .= ")"; + $resql = $db->query($sql); + while ($obj = $db->fetch_object($resql)) { + $rigthsarray []= $obj->id; + } + } elseif ($module != '') { + $sql = "SELECT id"; + $sql .= " FROM ".MAIN_DB_PREFIX."rights_def"; + $sql .= " WHERE entity IN (".$db->sanitize($token->entity, 0, 0, 0, 0).")"; + if ($module != 'allmodules') { + $sql .= " AND (module='".$db->escape($module)."')"; + } + $resql = $db->query($sql); + while ($obj = $db->fetch_object($resql)) { + $rigthsarray []= $obj->id; + } } } if ($action == 'addrights' && $caneditperms && $confirm == 'yes') { From 01296323d8d669c7585dccf7808a4c5a0405875f Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 25 Jun 2025 15:08:29 +0200 Subject: [PATCH 012/387] feat: enabling all perms will not enable unallowed global perms --- htdocs/user/api_token/card.php | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index dcf3664332b..4648b170bc3 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -104,7 +104,7 @@ if (empty($reshook)) { if (in_array($action, array('addrights', 'delrights'))) { $rigthsarray = []; - if ($rights != '') { + if (!empty($rights)) { $sql = "SELECT module, perms, subperms"; $sql .= " FROM ".$db->prefix()."rights_def"; $sql .= " WHERE id = ".((int) $rights); @@ -146,13 +146,24 @@ if (empty($reshook)) { while ($obj = $db->fetch_object($resql)) { $rigthsarray []= $obj->id; } - } elseif ($module != '') { + } elseif (!empty($module)) { $sql = "SELECT id"; $sql .= " FROM ".MAIN_DB_PREFIX."rights_def"; $sql .= " WHERE entity IN (".$db->sanitize($token->entity, 0, 0, 0, 0).")"; if ($module != 'allmodules') { $sql .= " AND (module='".$db->escape($module)."')"; } + // To enable only all perms in a module that a user has to avoid better perms in token + $sql .= " AND id IN ("; + $sql .= " SELECT ur.fk_id"; + $sql .= " FROM llx_user_rights as ur"; + $sql .= " WHERE ur.entity = ".$token->entity; + $sql .= " AND ur.fk_user = ".$id; + $sql .= " UNION"; + $sql .= " SELECT gr.fk_id"; + $sql .= " FROM llx_usergroup_rights as gr"; + $sql .= " WHERE EXISTS(SELECT gu.rowid FROM llx_usergroup_user as gu WHERE gu.fk_user = ".$id; + $sql .= " AND gu.fk_usergroup = gr.fk_usergroup))"; $resql = $db->query($sql); while ($obj = $db->fetch_object($resql)) { $rigthsarray []= $obj->id; @@ -160,7 +171,12 @@ if (empty($reshook)) { } } if ($action == 'addrights' && $caneditperms && $confirm == 'yes') { - $tokenrigthsarray = explode(',', $token->rights); + $tokenrigthsarray = []; + + if (!empty($token->rights)) { + $tokenrigthsarray = explode(',', $token->rights); + } + if (isset($rigthsarray)) { $tokenrigthsarray = array_merge($tokenrigthsarray, $rigthsarray); } else { From 98fd33108585c14cbbc7d5063311282c082497a9 Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 25 Jun 2025 15:17:13 +0200 Subject: [PATCH 013/387] feat: improve list display and show numperms and not string --- htdocs/user/api_token/list.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index d5a3895b27e..6a3d13ad901 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -196,7 +196,7 @@ if (empty($reshook)) { print ''; print ''; - $sql = "SELECT ot.rowid as token_id, ot.token, ot.entity, ot.state, ot.datec, ot.tms"; + $sql = "SELECT ot.rowid as token_id, ot.token, ot.entity, ot.state as rights, ot.datec as date_creation, ot.tms as date_modification"; $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; $sql .= " WHERE ot.fk_user = ".((int) $object->id); @@ -205,6 +205,11 @@ if (empty($reshook)) { // List of groups of user if ($db->num_rows($resql) > 0) { while ($obj = $db->fetch_object($resql)) { + // Compute number of perms + $numperms = 0; + if (!empty($obj->rights)) { + $numperms = count(explode(",", $obj->rights)); + } print ''; // Action column if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { @@ -219,7 +224,7 @@ if (empty($reshook)) { print ''; } print ''; @@ -227,13 +232,13 @@ if (empty($reshook)) { print $obj->entity; print ''; print ''; print ''; print ''; print ''; } From 357eaeb53f0a2a59f89177c641449bce92e08cd3 Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 25 Jun 2025 15:22:10 +0200 Subject: [PATCH 014/387] feat: change place of api tab --- htdocs/core/lib/usergroups.lib.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/htdocs/core/lib/usergroups.lib.php b/htdocs/core/lib/usergroups.lib.php index 1e698c743dd..d1b3430df05 100644 --- a/htdocs/core/lib/usergroups.lib.php +++ b/htdocs/core/lib/usergroups.lib.php @@ -159,6 +159,13 @@ function user_prepare_head(User $object) $h++; } + if (!empty($object->api_key)) { + $head[$h][0] = DOL_URL_ROOT.'/user/api_token/list.php?id='.$object->id; + $head[$h][1] = $langs->trans("ApiToken"); + $head[$h][2] = 'apitoken'; + $h++; + } + // Such info on users is visible only by internal user if (empty($user->socid)) { // Notes @@ -226,13 +233,6 @@ function user_prepare_head(User $object) $h++; } - if (!empty($object->api_key)) { - $head[$h][0] = DOL_URL_ROOT.'/user/api_token/list.php?id='.$object->id; - $head[$h][1] = $langs->trans("ApiToken"); - $head[$h][2] = 'apitoken'; - $h++; - } - complete_head_from_modules($conf, $langs, $object, $head, $h, 'user', 'remove'); return $head; From d44aa146f4f39236880f4d8cc4fbdb0a20c6e18d Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 25 Jun 2025 16:37:48 +0200 Subject: [PATCH 015/387] feat: loadRights now loads rights of token if specified --- htdocs/user/class/user.class.php | 298 +++++++++++++++++++------------ 1 file changed, 181 insertions(+), 117 deletions(-) diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 8bb71b642de..a0b3885101d 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -1305,10 +1305,11 @@ class User extends CommonObject * * @param string $moduletag Limit permission for a particular module ('' by default means load all permissions) * @param int $forcereload Force reload of permissions even if they were already loaded (ignore cache) + * @param string $token Load only permissions of the token * @return void * @see clearrights(), delrights(), addrights(), hasRight() */ - public function loadRights($moduletag = '', $forcereload = 0) + public function loadRights($moduletag = '', $forcereload = 0, $token = '') { global $conf; @@ -1337,133 +1338,120 @@ class User extends CommonObject // Get permission of users + Get permissions of groups if (!$alreadyloaded) { - // First user permissions - $sql = "SELECT DISTINCT r.module, r.perms, r.subperms"; - $sql .= " FROM ".$this->db->prefix()."user_rights as ur,"; - $sql .= " ".$this->db->prefix()."rights_def as r"; - $sql .= " WHERE r.id = ur.fk_id"; - if (getDolGlobalString('MULTICOMPANY_BACKWARD_COMPATIBILITY')) { - // On old version, we used entity defined into table r only - // @FIXME Test on MULTICOMPANY_BACKWARD_COMPATIBILITY is a very strange business rules because the select should be always the - // same than into user->loadRights() in user/perms.php and user/group/perms.php - // We should never use and remove this case. - $sql .= " AND r.entity IN (0,".(isModEnabled('multicompany') && getDolGlobalString('MULTICOMPANY_TRANSVERSE_MODE') ? "1," : "").$conf->entity.")"; - } else { - // On table r=rights_def, the unique key is (id, entity) because id is hard coded into module descriptor and inserted during module activation. - // So we must include the filter on entity on both table r. and ur. - $sql .= " AND r.entity = ".((int) $conf->entity)." AND ur.entity = ".((int) $conf->entity); - } - $sql .= " AND ur.fk_user = ".((int) $this->id); - $sql .= " AND r.perms IS NOT NULL"; - if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { - $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled - } - if ($moduletag) { - $sql .= " AND r.module = '".$this->db->escape($moduletag)."'"; - } + if (!empty($token)) { // If token specified, we only load perms from it + $sql = "SELECT ot.state as rights, ot.entity"; + $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; + $sql .= " WHERE ot.token = '".($this->db->escape($token))."'"; - $resql = $this->db->query($sql); - if ($resql) { - $num = $this->db->num_rows($resql); - $i = 0; - while ($i < $num) { - $obj = $this->db->fetch_object($resql); + $resql = $this->db->query($sql); + if ($resql) { + $tokenobj = $this->db->fetch_object($resql); + $this->db->free($resql); - if ($obj) { - $module = $obj->module; - $perms = $obj->perms; - $subperms = $obj->subperms; + $sql = "SELECT r.module, r.perms, r.subperms"; + $sql .= " FROM llx_rights_def as r"; + $sql .= " WHERE r.id IN (".$tokenobj->rights.")"; + $sql .= " AND r.entity = ".$tokenobj->entity; // TODO : Check if working with multicompany and if MULTICOMPANY_BACKWARD_COMPATIBILITY is needed + $sql .= " AND r.perms IS NOT NULL"; + if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { + $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled + } + if ($moduletag) { + $sql .= " AND r.module = '" . $this->db->escape($moduletag) . "'"; + } - if (!empty($perms)) { - if (!empty($module)) { - if (!isset($this->rights->$module) || !is_object($this->rights->$module)) { - $this->rights->$module = new stdClass(); - } - if (!empty($subperms)) { - if (!isset($this->rights->$module->$perms) || !is_object($this->rights->$module->$perms)) { - $this->rights->$module->$perms = new stdClass(); + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + while ($i < $num) { + $obj = $this->db->fetch_object($resql); + + if ($obj) { + $module = $obj->module; + $perms = $obj->perms; + $subperms = $obj->subperms; + + if (!empty($perms)) { + if (!empty($module)) { + if (!isset($this->rights->$module) || !is_object($this->rights->$module)) { + $this->rights->$module = new stdClass(); + } + if (!empty($subperms)) { + if (!isset($this->rights->$module->$perms) || !is_object($this->rights->$module->$perms)) { + $this->rights->$module->$perms = new stdClass(); + } + if (empty($this->rights->$module->$perms->$subperms)) { // if not already counted + $this->nb_rights++; + } + $this->rights->$module->$perms->$subperms = 1; + } else { + if (empty($this->rights->$module->$perms)) { // if not already counted + $this->nb_rights++; + } + $this->rights->$module->$perms = 1; + } } - if (empty($this->rights->$module->$perms->$subperms)) { // if not already counted - $this->nb_rights++; - } - $this->rights->$module->$perms->$subperms = 1; - } else { - if (empty($this->rights->$module->$perms)) { // if not already counted - $this->nb_rights++; - } - $this->rights->$module->$perms = 1; } } + $i++; } + $this->db->free($resql); } - $i++; } - $this->db->free($resql); - } - - // Now permissions of groups - $sql = "SELECT DISTINCT r.module, r.perms, r.subperms, r.entity"; - $sql .= " FROM ".$this->db->prefix()."usergroup_rights as gr,"; - $sql .= " ".$this->db->prefix()."usergroup_user as gu,"; - $sql .= " ".$this->db->prefix()."rights_def as r"; - $sql .= " WHERE r.id = gr.fk_id"; - if (getDolGlobalString('MULTICOMPANY_BACKWARD_COMPATIBILITY')) { - // @FIXME Test on MULTICOMPANY_BACKWARD_COMPATIBILITY is a very strange business rules because the select should be always the - // same than into user->loadRights() in user/perms.php and user/group/perms.php - // We should never use and remove this case. - if (isModEnabled('multicompany') && getDolGlobalString('MULTICOMPANY_TRANSVERSE_MODE')) { - $sql .= " AND gu.entity IN (0,".$conf->entity.")"; + } else { // If no token, we load user and groups perms + // First user permissions + $sql = "SELECT DISTINCT r.module, r.perms, r.subperms"; + $sql .= " FROM " . $this->db->prefix() . "user_rights as ur,"; + $sql .= " " . $this->db->prefix() . "rights_def as r"; + $sql .= " WHERE r.id = ur.fk_id"; + if (getDolGlobalString('MULTICOMPANY_BACKWARD_COMPATIBILITY')) { + // On old version, we used entity defined into table r only + // @FIXME Test on MULTICOMPANY_BACKWARD_COMPATIBILITY is a very strange business rules because the select should be always the + // same than into user->loadRights() in user/perms.php and user/group/perms.php + // We should never use and remove this case. + $sql .= " AND r.entity IN (0," . (isModEnabled('multicompany') && getDolGlobalString('MULTICOMPANY_TRANSVERSE_MODE') ? "1," : "") . $conf->entity . ")"; } else { - $sql .= " AND r.entity = ".((int) $conf->entity); + // On table r=rights_def, the unique key is (id, entity) because id is hard coded into module descriptor and inserted during module activation. + // So we must include the filter on entity on both table r. and ur. + $sql .= " AND r.entity = " . ((int)$conf->entity) . " AND ur.entity = " . ((int)$conf->entity); + } + $sql .= " AND ur.fk_user = " . ((int)$this->id); + $sql .= " AND r.perms IS NOT NULL"; + if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { + $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled + } + if ($moduletag) { + $sql .= " AND r.module = '" . $this->db->escape($moduletag) . "'"; } - } else { - $sql .= " AND gr.entity = ".((int) $conf->entity); // Only groups created in current entity - // The entity on the table gu=usergroup_user should be useless and should never be used because it is already into gr and r. - // but when using MULTICOMPANY_TRANSVERSE_MODE, we may have inserted record that make rubbish result here due to the duplicate record of - // other entities, so we are forced to add a filter on gu here - $sql .= " AND gu.entity IN (0,".$conf->entity.")"; - $sql .= " AND r.entity = ".((int) $conf->entity); // Only permission of modules enabled in current entity - } - // End of strange business rule - $sql .= " AND gr.fk_usergroup = gu.fk_usergroup"; - $sql .= " AND gu.fk_user = ".((int) $this->id); - $sql .= " AND r.perms IS NOT NULL"; - if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { - $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled - } - if ($moduletag) { - $sql .= " AND r.module = '".$this->db->escape($moduletag)."'"; - } - $resql = $this->db->query($sql); - if ($resql) { - $num = $this->db->num_rows($resql); - $i = 0; - while ($i < $num) { - $obj = $this->db->fetch_object($resql); + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + while ($i < $num) { + $obj = $this->db->fetch_object($resql); - if ($obj) { - $module = $obj->module; - $perms = $obj->perms; - $subperms = $obj->subperms; + if ($obj) { + $module = $obj->module; + $perms = $obj->perms; + $subperms = $obj->subperms; - if (!empty($perms)) { - if (!empty($module)) { - if (!isset($this->rights->$module) || !is_object($this->rights->$module)) { - $this->rights->$module = new stdClass(); - } - if (!empty($subperms)) { - if (!isset($this->rights->$module->$perms) || !is_object($this->rights->$module->$perms)) { - $this->rights->$module->$perms = new stdClass(); + if (!empty($perms)) { + if (!empty($module)) { + if (!isset($this->rights->$module) || !is_object($this->rights->$module)) { + $this->rights->$module = new stdClass(); } - if (empty($this->rights->$module->$perms->$subperms)) { // if not already counted - $this->nb_rights++; - } - $this->rights->$module->$perms->$subperms = 1; - } else { - // if we have already define a subperm like this $this->rights->$module->level1->level2 with llx_user_rights, we don't want override level1 because the level2 can be not define on user group - if (!isset($this->rights->$module->$perms) || !is_object($this->rights->$module->$perms)) { - if (empty($this->rights->$module->$perms)) { // if not already counted + if (!empty($subperms)) { + if (!isset($this->rights->$module->$perms) || !is_object($this->rights->$module->$perms)) { + $this->rights->$module->$perms = new stdClass(); + } + if (empty($this->rights->$module->$perms->$subperms)) { // if not already counted + $this->nb_rights++; + } + $this->rights->$module->$perms->$subperms = 1; + } else { + if (empty($this->rights->$module->$perms)) { // if not already counted $this->nb_rights++; } $this->rights->$module->$perms = 1; @@ -1471,10 +1459,86 @@ class User extends CommonObject } } } + $i++; } - $i++; + $this->db->free($resql); + } + + // Now permissions of groups + $sql = "SELECT DISTINCT r.module, r.perms, r.subperms, r.entity"; + $sql .= " FROM " . $this->db->prefix() . "usergroup_rights as gr,"; + $sql .= " " . $this->db->prefix() . "usergroup_user as gu,"; + $sql .= " " . $this->db->prefix() . "rights_def as r"; + $sql .= " WHERE r.id = gr.fk_id"; + if (getDolGlobalString('MULTICOMPANY_BACKWARD_COMPATIBILITY')) { + // @FIXME Test on MULTICOMPANY_BACKWARD_COMPATIBILITY is a very strange business rules because the select should be always the + // same than into user->loadRights() in user/perms.php and user/group/perms.php + // We should never use and remove this case. + if (isModEnabled('multicompany') && getDolGlobalString('MULTICOMPANY_TRANSVERSE_MODE')) { + $sql .= " AND gu.entity IN (0," . $conf->entity . ")"; + } else { + $sql .= " AND r.entity = " . ((int)$conf->entity); + } + } else { + $sql .= " AND gr.entity = " . ((int)$conf->entity); // Only groups created in current entity + // The entity on the table gu=usergroup_user should be useless and should never be used because it is already into gr and r. + // but when using MULTICOMPANY_TRANSVERSE_MODE, we may have inserted record that make rubbish result here due to the duplicate record of + // other entities, so we are forced to add a filter on gu here + $sql .= " AND gu.entity IN (0," . $conf->entity . ")"; + $sql .= " AND r.entity = " . ((int)$conf->entity); // Only permission of modules enabled in current entity + } + // End of strange business rule + $sql .= " AND gr.fk_usergroup = gu.fk_usergroup"; + $sql .= " AND gu.fk_user = " . ((int)$this->id); + $sql .= " AND r.perms IS NOT NULL"; + if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { + $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled + } + if ($moduletag) { + $sql .= " AND r.module = '" . $this->db->escape($moduletag) . "'"; + } + + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + while ($i < $num) { + $obj = $this->db->fetch_object($resql); + + if ($obj) { + $module = $obj->module; + $perms = $obj->perms; + $subperms = $obj->subperms; + + if (!empty($perms)) { + if (!empty($module)) { + if (!isset($this->rights->$module) || !is_object($this->rights->$module)) { + $this->rights->$module = new stdClass(); + } + if (!empty($subperms)) { + if (!isset($this->rights->$module->$perms) || !is_object($this->rights->$module->$perms)) { + $this->rights->$module->$perms = new stdClass(); + } + if (empty($this->rights->$module->$perms->$subperms)) { // if not already counted + $this->nb_rights++; + } + $this->rights->$module->$perms->$subperms = 1; + } else { + // if we have already define a subperm like this $this->rights->$module->level1->level2 with llx_user_rights, we don't want override level1 because the level2 can be not define on user group + if (!isset($this->rights->$module->$perms) || !is_object($this->rights->$module->$perms)) { + if (empty($this->rights->$module->$perms)) { // if not already counted + $this->nb_rights++; + } + $this->rights->$module->$perms = 1; + } + } + } + } + } + $i++; + } + $this->db->free($resql); } - $this->db->free($resql); } // Force permission on user for admin @@ -1566,9 +1630,9 @@ class User extends CommonObject * @see clearrights(), delrights(), addrights(), hasRight() * @phpstan-ignore-next-line */ - public function getrights($moduletag = '', $forcereload = 0) + public function getrights($moduletag = '', $forcereload = 0, $token = '') { - $this->loadRights($moduletag, $forcereload); + $this->loadRights($moduletag, $forcereload, $token); } /** From eece31093de4d15e7e879dbb78ec1f84b21d1d5f Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 25 Jun 2025 16:47:21 +0200 Subject: [PATCH 016/387] feat: avoid dupe perms --- htdocs/user/api_token/card.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 4648b170bc3..f8668b85051 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -170,6 +170,7 @@ if (empty($reshook)) { } } } + if ($action == 'addrights' && $caneditperms && $confirm == 'yes') { $tokenrigthsarray = []; @@ -183,6 +184,7 @@ if (empty($reshook)) { $tokenrigthsarray []= $rights; } + $tokenrigthsarray = array_unique($tokenrigthsarray); sort($tokenrigthsarray); $newrigths = preg_replace('/\s+/', '', implode(',', $tokenrigthsarray)); @@ -206,6 +208,7 @@ if (empty($reshook)) { $tokenrigthsarray = array_diff($tokenrigthsarray, array($rights)); } + $tokenrigthsarray = array_unique($tokenrigthsarray); sort($tokenrigthsarray); $newrigths = preg_replace('/\s+/', '', implode(',', $tokenrigthsarray)); From 4a1fea5e07615dbb9e76ebda8c2f90efca75a446 Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 25 Jun 2025 17:29:05 +0200 Subject: [PATCH 017/387] feat: change token modif form to only delete button --- htdocs/langs/en_US/users.lang | 2 ++ htdocs/user/api_token/card.php | 48 +++++++++++++++++++++------------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/htdocs/langs/en_US/users.lang b/htdocs/langs/en_US/users.lang index 19402459a7a..56c4f99a6cb 100644 --- a/htdocs/langs/en_US/users.lang +++ b/htdocs/langs/en_US/users.lang @@ -148,3 +148,5 @@ ApiToken=Api token ListOfTokensForUser=List of tokens for this user ListOfRightsForToken=List of rights for this token NumberOfPermissions=Number of permissions +DeleteToken=Delete token +ConfirmDeleteToken=Are you sure you want to delete this token? diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index f8668b85051..50a3ac1fa12 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -223,6 +223,21 @@ if (empty($reshook)) { $token->rights = $newrigths; } + + elseif ($action == 'confirm_delete' && $confirm == 'yes' && $caneditfield) { + // Remove token + $sql = "DELETE FROM ".MAIN_DB_PREFIX."oauth_token"; + $sql .= " WHERE rowid = '".$tokenid."'"; + + $resql = $db->query($sql); + + if ($resql) { + header('Location: list.php?id='.$object->id); + exit; + } else { + dol_print_error($db); + } + } } @@ -236,6 +251,14 @@ $help_url = ''; llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-user page-card_param_ihm'); +$formconfirm = ''; + +if ($action == 'delete') { + $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id.'&tokenid='.$token->token_id, $langs->trans('DeleteToken'), $langs->trans('ConfirmDeleteToken'), 'confirm_delete', '', 0, 1); +} + +print $formconfirm; + $arrayofselected = is_array($toselect) ? $toselect : array(); $head = user_prepare_head($object); @@ -255,11 +278,7 @@ $morehtmlref .= dolButtonToOpenUrlInDialogPopup('publicvirtualcard', $langs->tra dol_banner_tab($object, 'id', $linkback, $user->hasRight("user", "user", "read") || $user->admin, 'rowid', 'ref', $morehtmlref); -print '
'."\n"; -print ''; -print ''; -print ''; - +// Tokens info print '
'; print '
'; print '
'; print ''; @@ -651,7 +680,7 @@ if ($result) { // Special warning case for the permission "Allow to modify other users password" if ($obj->module == 'user' && $obj->perms == 'user' && $obj->subperms == 'password') { if ((!empty($object->admin) && !empty($objMod->rights_admin_allowed)) || - in_array($obj->id, $permsuser) /* if edited user owns this permissions */ || + in_array($obj->id, $tokenperms) /* if edited user owns this permissions */ || (isset($permsgroupbyentitypluszero) && is_array($permsgroupbyentitypluszero) && in_array($obj->id, $permsgroupbyentitypluszero))) { print ' '.img_warning($langs->trans("AllowPasswordResetBySendingANewPassByEmail")); } @@ -659,7 +688,7 @@ if ($result) { // Special warning case for the permission "Create/modify other users, groups and permissions" if ($obj->module == 'user' && $obj->perms == 'user' && ($obj->subperms == 'creer' || $obj->subperms == 'create')) { if ((!empty($object->admin) && !empty($objMod->rights_admin_allowed)) || - in_array($obj->id, $permsuser) /* if edited user owns this permissions */ || + in_array($obj->id, $tokenperms) /* if edited user owns this permissions */ || (isset($permsgroupbyentitypluszero) && is_array($permsgroupbyentitypluszero) && in_array($obj->id, $permsgroupbyentitypluszero))) { print ' '.img_warning($langs->trans("AllowAnyPrivileges")); } From de1e05e618d6d3ac78f08bdbddb5dd5d012ebfa3 Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 25 Jun 2025 14:39:59 +0200 Subject: [PATCH 010/387] feat: changed redirect url that was not working --- htdocs/user/api_token/card.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 8fd61cd5fea..2b044e31bcb 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -324,9 +324,9 @@ print '
'.$langs->trans("Module").''; - print ''.$langs->trans("All").""; + print ''.$langs->trans("All").""; print ' / '; - print ''.$langs->trans("None").""; + print ''.$langs->trans("None").""; print ''.$langs->trans("DateModification").'
'; - print ''.img_object($langs->trans("ShowToken"), "email").' '; // TODO : change icon + print ''; print $obj->token; print ''; print ''; - print $obj->state; + print $numperms; print ''; - print $obj->datec; + print $obj->date_creation; print ''; - print $obj->tms; + print $obj->date_modification; print '
'; @@ -285,46 +304,39 @@ if (!empty($object->ldap_sid) && $object->status == 0) { } print ''."\n"; -//var_dump(showValueWithClipboardCPButton($token->token, 1, $token->token)); - // Token print ''; print ''; print ''."\n"; // Entity print ''; print ''; print ''."\n"; // Creation date print ''; print ''; print ''."\n"; // Modification date print ''; print ''; print ''."\n"; print '
'.$langs->trans("ApiToken").''; -print ''; -if (!empty($conf->use_javascript_ajax)) { - print img_picto($langs->transnoentities('Generate'), 'refresh', 'id="generate_api_key" class="linkobject paddingleft"'); -} +print showValueWithClipboardCPButton($token->token, 1, $token->token); print '
'.$langs->trans("Entity").''; -print ''; +print $token->entity; print '
'.$langs->trans("DateCreation").''; -print ''; +print $token->date_creation; print '
'.$langs->trans("DateModification").''; -print ''; +print $token->date_modification; print '
'; -print '
'; -print ''; -print ''.$langs->trans("Delete").''; +print '
'; +print dolGetButtonAction($langs->trans('Delete'), '', 'delete', $_SERVER["PHP_SELF"].'?id='.$object->id.'&tokenid='.$token->token_id.'&action=delete&token='.newToken(), '', $caneditperms); print '
'; print '
'; -print ''; print dol_get_fiche_end(); From 526de3276aa935e5e213793ef44c2ed5fc4cc2f7 Mon Sep 17 00:00:00 2001 From: yannis Date: Thu, 26 Jun 2025 09:23:24 +0200 Subject: [PATCH 018/387] feat: disable prev/next nav in token card --- htdocs/user/api_token/card.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 50a3ac1fa12..83017864be8 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -276,7 +276,8 @@ $morehtmlref .= ''; $urltovirtualcard = '/user/virtualcard.php?id='.((int) $object->id); $morehtmlref .= dolButtonToOpenUrlInDialogPopup('publicvirtualcard', $langs->transnoentitiesnoconv("PublicVirtualCardUrl").' - '.$object->getFullName($langs), img_picto($langs->trans("PublicVirtualCardUrl"), 'card', 'class="valignmiddle marginleftonly paddingrightonly"'), $urltovirtualcard, '', 'nohover'); -dol_banner_tab($object, 'id', $linkback, $user->hasRight("user", "user", "read") || $user->admin, 'rowid', 'ref', $morehtmlref); +// Disabled prev/next because there is no token object +dol_banner_tab($object, '', $linkback, $user->hasRight("user", "user", "read") || $user->admin, 'none', '', $morehtmlref); // Tokens info print '
'; From 7e163656ead97de999affe8be33c8a038e08d0ee Mon Sep 17 00:00:00 2001 From: yannis Date: Thu, 26 Jun 2025 10:57:52 +0200 Subject: [PATCH 019/387] feat: creation form and database storage --- htdocs/user/api_token/card.php | 1069 +++++++++++++++++--------------- htdocs/user/api_token/list.php | 2 +- 2 files changed, 571 insertions(+), 500 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 83017864be8..b2ae328c246 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -42,6 +42,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php'; // Load translation files required by page $langs->loadLangs(array('admin', 'users')); +$error = 0; // Security check $id = GETPOSTINT('id'); @@ -50,6 +51,8 @@ $tokenid = GETPOST('tokenid', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); $module = GETPOST('module', 'alpha'); $rights = GETPOSTINT('rights'); +$cancel = GETPOST('cancel', 'alpha'); +$backtopage = GETPOST('backtopage', 'alpha'); if (!isset($id) || empty($id)) { accessforbidden(); @@ -101,6 +104,14 @@ if ($reshook < 0) { } if (empty($reshook)) { + if ($cancel) { + if (!empty($backtopage)) { + header("Location: ".$backtopage); + exit; + } + $action = ''; + } + if (in_array($action, array('addrights', 'delrights'))) { $rigthsarray = []; @@ -224,7 +235,33 @@ if (empty($reshook)) { $token->rights = $newrigths; } - elseif ($action == 'confirm_delete' && $confirm == 'yes' && $caneditfield) { + if ($action == 'add') { + $tokenstring = GETPOST('api_key', 'alphanohtml'); + + if (empty($tokenstring)) { + setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("ApiToken")), null, 'errors'); + $action = 'create'; + $error++; + } + + if (!$error) { + $db->begin(); + + $sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token (service, token, fk_user, entity, datec)"; + $sql .= " VALUES ('dolibarr_rest_api', '".$db->escape($tokenstring)."', ".($id).", ".((int) $conf->entity).", '".$db->idate(dol_now())."')"; + $resql = $db->query($sql); + + if (!$resql) { + dol_print_error($db); + $db->rollback(); + } else { + $insertedtokenid = $db->last_insert_id(MAIN_DB_PREFIX."oauth_token"); + $db->commit(); + header("Location: ".$_SERVER["PHP_SELF"].'?id='.$object->id.'&tokenid='.$insertedtokenid); + exit; + } + } + } elseif ($action == 'confirm_delete' && $confirm == 'yes' && $caneditfield) { // Remove token $sql = "DELETE FROM ".MAIN_DB_PREFIX."oauth_token"; $sql .= " WHERE rowid = '".$tokenid."'"; @@ -279,424 +316,472 @@ $morehtmlref .= dolButtonToOpenUrlInDialogPopup('publicvirtualcard', $langs->tra // Disabled prev/next because there is no token object dol_banner_tab($object, '', $linkback, $user->hasRight("user", "user", "read") || $user->admin, 'none', '', $morehtmlref); -// Tokens info -print '
'; -print '
'; -print ''; +if ($action == 'create') { + print ''; + print ''; + print ''; + print ''; -// Login -print ''; -if (!empty($object->ldap_sid) && $object->status == 0) { - print ''; -} else { + print dol_get_fiche_head(); + + print '
'.$langs->trans("Login").''; - print $langs->trans("LoginAccountDisableInDolibarr"); - print '
'; + + print ''; + print ''; + + print ''; print ''; + print "
'.$langs->trans('User').''.$person_name.'
'.$langs->trans('Entity').''.$conf->entity.'
'.$langs->trans("ApiToken").''; - $addadmin = ''; - if (property_exists($object, 'admin')) { - if (isModEnabled('multicompany') && !empty($object->admin) && empty($object->entity)) { - $addadmin .= img_picto($langs->trans("SuperAdministratorDesc"), "redstar", 'class="paddingleft valignmiddle"'); - } elseif (!empty($object->admin)) { - $addadmin .= img_picto($langs->trans("AdministratorDesc"), "star", 'class="paddingleft valignmiddle"'); - } + print ''; + if (!empty($conf->use_javascript_ajax)) { + print img_picto($langs->transnoentities('Generate'), 'refresh', 'id="generate_api_key" class="linkobject paddingleft"'); } - print showValueWithClipboardCPButton($object->login).$addadmin; + print '
\n"; + + print dol_get_fiche_end(); + + print '
'; + print ''; + print ''; + print '
'; + + print ""; + +} elseif ($id > 0 && !empty($token)) { + // Tokens info + print '
'; + print '
'; + print ''; + + // Login + print ''; + if (!empty($object->ldap_sid) && $object->status == 0) { + print ''; + } else { + print ''; + } + print ''."\n"; + + // Token + print ''; + print ''; -} -print ''."\n"; + print ''."\n"; -// Token -print ''; -print ''; -print ''."\n"; + // Entity + print ''; + print ''; + print ''."\n"; -// Entity -print ''; -print ''; -print ''."\n"; + // Creation date + print ''; + print ''; + print ''."\n"; -// Creation date -print ''; -print ''; -print ''."\n"; + // Modification date + print ''; + print ''; + print ''."\n"; -// Modification date -print ''; -print ''; -print ''."\n"; + print '
'.$langs->trans("Login").''; + print $langs->trans("LoginAccountDisableInDolibarr"); + print ''; + $addadmin = ''; + if (property_exists($object, 'admin')) { + if (isModEnabled('multicompany') && !empty($object->admin) && empty($object->entity)) { + $addadmin .= img_picto($langs->trans("SuperAdministratorDesc"), "redstar", 'class="paddingleft valignmiddle"'); + } elseif (!empty($object->admin)) { + $addadmin .= img_picto($langs->trans("AdministratorDesc"), "star", 'class="paddingleft valignmiddle"'); + } + } + print showValueWithClipboardCPButton($object->login).$addadmin; + print '
'.$langs->trans("ApiToken").''; + print showValueWithClipboardCPButton($token->token, 1, $token->token); print '
'.$langs->trans("ApiToken").''; -print showValueWithClipboardCPButton($token->token, 1, $token->token); -print '
'.$langs->trans("Entity").''; + print $token->entity; + print '
'.$langs->trans("Entity").''; -print $token->entity; -print '
'.$langs->trans("DateCreation").''; + print $token->date_creation; + print '
'.$langs->trans("DateCreation").''; -print $token->date_creation; -print '
'.$langs->trans("DateModification").''; + print $token->date_modification; + print '
'.$langs->trans("DateModification").''; -print $token->date_modification; -print '
'; + print '
'; + print dolGetButtonAction($langs->trans('Delete'), '', 'delete', $_SERVER["PHP_SELF"].'?id='.$object->id.'&tokenid='.$token->token_id.'&action=delete&token='.newToken(), '', $caneditperms); + print '
'; + print '
'; -print '
'; -print '
'; -print dolGetButtonAction($langs->trans('Delete'), '', 'delete', $_SERVER["PHP_SELF"].'?id='.$object->id.'&tokenid='.$token->token_id.'&action=delete&token='.newToken(), '', $caneditperms); -print '
'; -print '
'; + print dol_get_fiche_end(); -print dol_get_fiche_end(); - -print load_fiche_titre($langs->trans("ListOfRightsForToken"), '', ''); + print load_fiche_titre($langs->trans("ListOfRightsForToken"), '', ''); + // TODO : Rights part + print ''."\n"; + if ($user->admin) { + print info_admin($langs->trans("WarningOnlyPermissionOfActivatedModules")); + } - // TODO : Rights part -print ''."\n"; + $db->begin(); -if ($user->admin) { - print info_admin($langs->trans("WarningOnlyPermissionOfActivatedModules")); -} + // Search all modules with permission and reload permissions def. + $modules = array(); + $modulesdir = dolGetModulesDirs(); -$db->begin(); + foreach ($modulesdir as $dir) { + $handle = @opendir(dol_osencode($dir)); + if (is_resource($handle)) { + while (($file = readdir($handle)) !== false) { + if (is_readable($dir.$file) && substr($file, 0, 3) == 'mod' && substr($file, dol_strlen($file) - 10) == '.class.php') { + $modName = substr($file, 0, dol_strlen($file) - 10); -// Search all modules with permission and reload permissions def. -$modules = array(); -$modulesdir = dolGetModulesDirs(); + if ($modName) { + include_once $dir.$file; + $objMod = new $modName($db); + '@phan-var-force DolibarrModules $objMod'; -foreach ($modulesdir as $dir) { - $handle = @opendir(dol_osencode($dir)); - if (is_resource($handle)) { - while (($file = readdir($handle)) !== false) { - if (is_readable($dir.$file) && substr($file, 0, 3) == 'mod' && substr($file, dol_strlen($file) - 10) == '.class.php') { - $modName = substr($file, 0, dol_strlen($file) - 10); - - if ($modName) { - include_once $dir.$file; - $objMod = new $modName($db); - '@phan-var-force DolibarrModules $objMod'; - - // Load all lang files of module - if (isset($objMod->langfiles) && is_array($objMod->langfiles)) { - foreach ($objMod->langfiles as $domain) { - $langs->load($domain); + // Load all lang files of module + if (isset($objMod->langfiles) && is_array($objMod->langfiles)) { + foreach ($objMod->langfiles as $domain) { + $langs->load($domain); + } + } + // Load all permissions + if ($objMod->rights_class) { + $ret = $objMod->insert_permissions(0, $token->entity); + $modules[$objMod->rights_class] = $objMod; + //print "modules[".$objMod->rights_class."]=$objMod;"; } } - // Load all permissions - if ($objMod->rights_class) { - $ret = $objMod->insert_permissions(0, $token->entity); - $modules[$objMod->rights_class] = $objMod; - //print "modules[".$objMod->rights_class."]=$objMod;"; - } } } } } -} -$db->commit(); + $db->commit(); -// Load perms ids for the user -$tokenperms = explode(",", $token->rights); + // Load perms ids for the user + $tokenperms = explode(",", $token->rights); -print '
'; -print ''; + print '
'; + print '
'; -print ''; -print ''; -if ($caneditperms) { - print ''; -} else { + print ''; + print ''; + if ($caneditperms) { + print ''; + } else { + print ''; + } print ''; -} -print ''; -//print ''; -print ''; -print ''."\n"; + //print ''; + print ''; + print ''."\n"; -// Load modules rights and correct position if needed -$sql = "SELECT r.id, r.libelle as label, r.module, r.perms, r.subperms, r.module_position, r.bydefault"; -$sql .= " FROM ".MAIN_DB_PREFIX."rights_def as r"; -$sql .= " WHERE r.libelle NOT LIKE 'tou%'"; // On ignore droits "tous" -$sql .= " AND r.entity = ".((int) $entity); -$sql .= " ORDER BY r.family_position, r.module_position, r.module, r.id"; + // Load modules rights and correct position if needed + $sql = "SELECT r.id, r.libelle as label, r.module, r.perms, r.subperms, r.module_position, r.bydefault"; + $sql .= " FROM ".MAIN_DB_PREFIX."rights_def as r"; + $sql .= " WHERE r.libelle NOT LIKE 'tou%'"; // On ignore droits "tous" + $sql .= " AND r.entity = ".((int)$entity); + $sql .= " ORDER BY r.family_position, r.module_position, r.module, r.id"; -$result = $db->query($sql); -if ($result) { - $num = $db->num_rows($result); - $i = 0; - $oldmod = ''; + $result = $db->query($sql); + if ($result) { + $num = $db->num_rows($result); + $i = 0; + $oldmod = ''; - while ($i < $num) { - $obj = $db->fetch_object($result); + while ($i < $num) { + $obj = $db->fetch_object($result); - // If line is for a module that does not exist anymore (absent of includes/module), we ignore it - if (!isset($obj->module) || empty($modules[$obj->module])) { - $i++; - continue; - } - - // Special cases - if (isModEnabled("reception")) { - // The 2 permissions in fournisseur modules are replaced by permissions into reception module - if ($obj->module == 'fournisseur' && $obj->perms == 'commande' && $obj->subperms == 'receptionner') { + // If line is for a module that does not exist anymore (absent of includes/module), we ignore it + if (!isset($obj->module) || empty($modules[$obj->module])) { $i++; continue; } - if ($obj->module == 'fournisseur' && $obj->perms == 'commande_advance' && $obj->subperms == 'check') { - $i++; - continue; - } - } - $objMod = $modules[$obj->module]; - - // Save field module_position in database if value is wrong - if (empty($obj->module_position) || (is_object($objMod) && $objMod->isCoreOrExternalModule() == 'external' && $obj->module_position < 100000)) { - if (is_object($modules[$obj->module]) && ($modules[$obj->module]->module_position > 0)) { - // TODO Define familyposition - //$familyposition = $modules[$obj->module]->family_position; - $familyposition = 0; - - $newmoduleposition = $modules[$obj->module]->module_position; - - // Correct $newmoduleposition position for external modules - $objMod = $modules[$obj->module]; - if (is_object($objMod) && $objMod->isCoreOrExternalModule() == 'external' && $newmoduleposition < 100000) { - $newmoduleposition += 100000; + // Special cases + if (isModEnabled("reception")) { + // The 2 permissions in fournisseur modules are replaced by permissions into reception module + if ($obj->module == 'fournisseur' && $obj->perms == 'commande' && $obj->subperms == 'receptionner') { + $i++; + continue; } + if ($obj->module == 'fournisseur' && $obj->perms == 'commande_advance' && $obj->subperms == 'check') { + $i++; + continue; + } + } - $sqlupdate = 'UPDATE '.MAIN_DB_PREFIX."rights_def SET module_position = ".((int) $newmoduleposition).","; - $sqlupdate .= " family_position = ".((int) $familyposition); - $sqlupdate .= " WHERE module_position = ".((int) $obj->module_position)." AND module = '".$db->escape($obj->module)."'"; + $objMod = $modules[$obj->module]; - $db->query($sqlupdate); + // Save field module_position in database if value is wrong + if (empty($obj->module_position) || (is_object($objMod) && $objMod->isCoreOrExternalModule() == 'external' && $obj->module_position < 100000)) { + if (is_object($modules[$obj->module]) && ($modules[$obj->module]->module_position > 0)) { + // TODO Define familyposition + //$familyposition = $modules[$obj->module]->family_position; + $familyposition = 0; + + $newmoduleposition = $modules[$obj->module]->module_position; + + // Correct $newmoduleposition position for external modules + $objMod = $modules[$obj->module]; + if (is_object($objMod) && $objMod->isCoreOrExternalModule() == 'external' && $newmoduleposition < 100000) { + $newmoduleposition += 100000; + } + + $sqlupdate = 'UPDATE '.MAIN_DB_PREFIX."rights_def SET module_position = ".((int)$newmoduleposition).","; + $sqlupdate .= " family_position = ".((int)$familyposition); + $sqlupdate .= " WHERE module_position = ".((int)$obj->module_position)." AND module = '".$db->escape($obj->module)."'"; + + $db->query($sqlupdate); + } } } } -} -//Load perms ids for user and user's groups to show only the perms the user has and avoid better perms in the token. -$allusersperms = array(); + //Load perms ids for user and user's groups to show only the perms the user has and avoid better perms in the token. + $allusersperms = array(); -// Users perms -$sql = "SELECT ur.fk_id"; -$sql .= " FROM ".MAIN_DB_PREFIX."user_rights as ur"; -$sql .= " WHERE ur.entity = ".((int) $entity); -$sql .= " AND ur.fk_user = ".((int) $object->id); -$sql .= " UNION "; -// Groups perms -$sql .= "SELECT gr.fk_id"; -$sql .= " FROM ".MAIN_DB_PREFIX."usergroup_rights as gr"; -$sql .= " WHERE EXISTS(SELECT gu.rowid FROM llx_usergroup_user as gu WHERE gu.fk_user = ".((int) $id)." AND gu.fk_usergroup = gr.fk_usergroup)"; + // Users perms + $sql = "SELECT ur.fk_id"; + $sql .= " FROM ".MAIN_DB_PREFIX."user_rights as ur"; + $sql .= " WHERE ur.entity = ".((int)$entity); + $sql .= " AND ur.fk_user = ".((int)$object->id); + $sql .= " UNION "; + // Groups perms + $sql .= "SELECT gr.fk_id"; + $sql .= " FROM ".MAIN_DB_PREFIX."usergroup_rights as gr"; + $sql .= " WHERE EXISTS(SELECT gu.rowid FROM llx_usergroup_user as gu WHERE gu.fk_user = ".((int)$id)." AND gu.fk_usergroup = gr.fk_usergroup)"; -dol_syslog("get user perms", LOG_DEBUG); -$result = $db->query($sql); -if ($result) { - $num = $db->num_rows($result); - $i = 0; - while ($i < $num) { - $obj = $db->fetch_object($result); - array_push($allusersperms, $obj->fk_id); - $i++; - } - $db->free($result); -} else { - dol_print_error($db); -} - -// Load and show all the perms grouped by module - -//print "xx".$conf->global->MAIN_USE_ADVANCED_PERMS; -$sql = "SELECT r.id, r.libelle as label, r.module, r.perms, r.subperms, r.module_position, r.bydefault"; -$sql .= " FROM ".MAIN_DB_PREFIX."rights_def as r"; -$sql .= " WHERE r.libelle NOT LIKE 'tou%'"; // On ignore droits "tous" -$sql .= " AND r.entity = ".((int) $entity); -$sql .= " AND r.id IN (".implode(', ', $allusersperms).")"; -if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { - $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled -} -$sql .= " ORDER BY r.family_position, r.module_position, r.module, r.id"; - -$result = $db->query($sql); -if ($result) { - $num = $db->num_rows($result); - $i = 0; - $j = 0; - $oldmod = ''; - - $cookietohidegroup = (empty($_COOKIE["DOLUSER_PERMS_HIDE_GRP"]) ? '' : preg_replace('/^,/', '', $_COOKIE["DOLUSER_PERMS_HIDE_GRP"])); - $cookietohidegrouparray = explode(',', $cookietohidegroup); - //var_dump($cookietohidegrouparray); - - while ($i < $num) { - $obj = $db->fetch_object($result); - - // If line is for a module that does not exist anymore (absent of includes/module), we ignore it - if (empty($modules[$obj->module])) { + dol_syslog("get user perms", LOG_DEBUG); + $result = $db->query($sql); + if ($result) { + $num = $db->num_rows($result); + $i = 0; + while ($i < $num) { + $obj = $db->fetch_object($result); + array_push($allusersperms, $obj->fk_id); $i++; - continue; } + $db->free($result); + } else { + dol_print_error($db); + } - // Special cases - if (isModEnabled("reception")) { - // The 2 permission in fournisseur modules has been replaced by permissions into reception module - if ($obj->module == 'fournisseur' && $obj->perms == 'commande' && $obj->subperms == 'receptionner') { + // Load and show all the perms grouped by module + + //print "xx".$conf->global->MAIN_USE_ADVANCED_PERMS; + $sql = "SELECT r.id, r.libelle as label, r.module, r.perms, r.subperms, r.module_position, r.bydefault"; + $sql .= " FROM ".MAIN_DB_PREFIX."rights_def as r"; + $sql .= " WHERE r.libelle NOT LIKE 'tou%'"; // On ignore droits "tous" + $sql .= " AND r.entity = ".((int)$entity); + $sql .= " AND r.id IN (".implode(', ', $allusersperms).")"; + if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { + $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled + } + $sql .= " ORDER BY r.family_position, r.module_position, r.module, r.id"; + + $result = $db->query($sql); + if ($result) { + $num = $db->num_rows($result); + $i = 0; + $j = 0; + $oldmod = ''; + + $cookietohidegroup = (empty($_COOKIE["DOLUSER_PERMS_HIDE_GRP"]) ? '' : preg_replace('/^,/', '', $_COOKIE["DOLUSER_PERMS_HIDE_GRP"])); + $cookietohidegrouparray = explode(',', $cookietohidegroup); + //var_dump($cookietohidegrouparray); + + while ($i < $num) { + $obj = $db->fetch_object($result); + + // If line is for a module that does not exist anymore (absent of includes/module), we ignore it + if (empty($modules[$obj->module])) { $i++; continue; } - if ($obj->module == 'fournisseur' && $obj->perms == 'commande_advance' && $obj->subperms == 'check') { - $i++; - continue; + + // Special cases + if (isModEnabled("reception")) { + // The 2 permission in fournisseur modules has been replaced by permissions into reception module + if ($obj->module == 'fournisseur' && $obj->perms == 'commande' && $obj->subperms == 'receptionner') { + $i++; + continue; + } + if ($obj->module == 'fournisseur' && $obj->perms == 'commande_advance' && $obj->subperms == 'check') { + $i++; + continue; + } } - } - $objMod = $modules[$obj->module]; + $objMod = $modules[$obj->module]; - if (GETPOSTISSET('forbreakperms_'.$obj->module)) { - $ishidden = GETPOSTINT('forbreakperms_'.$obj->module); - } elseif (in_array($j, $cookietohidegrouparray)) { // If j is among list of hidden group - $ishidden = 1; - } else { - $ishidden = 0; - } - $isexpanded = ! $ishidden; - //var_dump("isexpanded=".$isexpanded); - - $permsgroupbyentitypluszero = array(); - if (!empty($permsgroupbyentity[0])) { - $permsgroupbyentitypluszero = array_merge($permsgroupbyentitypluszero, $permsgroupbyentity[0]); - } - if (!empty($permsgroupbyentity[$entity])) { - $permsgroupbyentitypluszero = array_merge($permsgroupbyentitypluszero, $permsgroupbyentity[$entity]); - } - //var_dump($permsgroupbyentitypluszero); - - // Break found, it's a new module to catch - if (isset($obj->module) && ($oldmod != $obj->module)) { - $oldmod = $obj->module; - - $j++; if (GETPOSTISSET('forbreakperms_'.$obj->module)) { $ishidden = GETPOSTINT('forbreakperms_'.$obj->module); - } elseif (in_array($j, $cookietohidegrouparray)) { // If j is among list of hidden group + } elseif (in_array($j, $cookietohidegrouparray)) { // If j is among list of hidden group $ishidden = 1; } else { $ishidden = 0; } - $isexpanded = ! $ishidden; - //var_dump('$obj->module='.$obj->module.' isexpanded='.$isexpanded); + $isexpanded = !$ishidden; + //var_dump("isexpanded=".$isexpanded); - // Break detected, we get objMod - $objMod = $modules[$obj->module]; - $picto = ($objMod->picto ? $objMod->picto : 'generic'); + $permsgroupbyentitypluszero = array(); + if (!empty($permsgroupbyentity[0])) { + $permsgroupbyentitypluszero = array_merge($permsgroupbyentitypluszero, $permsgroupbyentity[0]); + } + if (!empty($permsgroupbyentity[$entity])) { + $permsgroupbyentitypluszero = array_merge($permsgroupbyentitypluszero, $permsgroupbyentity[$entity]); + } + //var_dump($permsgroupbyentitypluszero); + + // Break found, it's a new module to catch + if (isset($obj->module) && ($oldmod != $obj->module)) { + $oldmod = $obj->module; + + $j++; + if (GETPOSTISSET('forbreakperms_'.$obj->module)) { + $ishidden = GETPOSTINT('forbreakperms_'.$obj->module); + } elseif (in_array($j, $cookietohidegrouparray)) { // If j is among list of hidden group + $ishidden = 1; + } else { + $ishidden = 0; + } + $isexpanded = !$ishidden; + //var_dump('$obj->module='.$obj->module.' isexpanded='.$isexpanded); + + // Break detected, we get objMod + $objMod = $modules[$obj->module]; + $picto = ($objMod->picto ? $objMod->picto : 'generic'); + + // Show break line + print ''; + // Picto and label of module + print ''; + + // Permission and tick (2 columns) + if (($caneditperms && empty($objMod->rights_admin_allowed)) || empty($object->admin)) { + if ($caneditperms) { + print ''; + print ''; + } else { + print ''; + print ''; + } + } else { + if ($caneditperms) { + print ''; + print ''; + } else { + print ''; + print ''; + } + } + + // Description of permission (2 columns) + print ''; + print ''; //Add picto + / - when open en closed + print ''."\n"; + } + + $permlabel = (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && ($langs->trans("PermissionAdvanced".$obj->id) != "PermissionAdvanced".$obj->id) ? $langs->trans("PermissionAdvanced".$obj->id) : (($langs->trans("Permission".$obj->id) != "Permission".$obj->id) ? $langs->trans("Permission".$obj->id) : $langs->trans($obj->label))); + + print ''."\n"; + print ''; - // Show break line - print ''; // Picto and label of module - print ''; // Permission and tick (2 columns) - if (($caneditperms && empty($objMod->rights_admin_allowed)) || empty($object->admin)) { + if (!empty($object->admin) && !empty($objMod->rights_admin_allowed)) { // Permission granted because admin + print ''; if ($caneditperms) { - print ''; - print ''; } else { - print ''; - print ''; - } - } else { - if ($caneditperms) { - print ''; - print ''; + } elseif (in_array($obj->id, $tokenperms)) { // Permission granted by user + print ''; + if ($caneditperms) { + print ''; } else { - print ''; - print ''; + print ''; } - } - - // Description of permission (2 columns) - print ''; - print ''; //Add picto + / - when open en closed - print ''."\n"; - } - - $permlabel = (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && ($langs->trans("PermissionAdvanced".$obj->id) != "PermissionAdvanced".$obj->id) ? $langs->trans("PermissionAdvanced".$obj->id) : (($langs->trans("Permission".$obj->id) != "Permission".$obj->id) ? $langs->trans("Permission".$obj->id) : $langs->trans($obj->label))); - - print ''."\n"; - print ''; - - // Picto and label of module - print ''; - - // Permission and tick (2 columns) - if (!empty($object->admin) && !empty($objMod->rights_admin_allowed)) { // Permission granted because admin - print ''; - if ($caneditperms) { - print ''; - } else { - print ''; - } - print ''; - } elseif (in_array($obj->id, $tokenperms)) { // Permission granted by user - print ''; - if ($caneditperms) { - print ''; - } else { - print ''; - } - print ''; - } elseif (isset($permsgroupbyentitypluszero) && is_array($permsgroupbyentitypluszero)) { - print ''; - if (in_array($obj->id, $permsgroupbyentitypluszero)) { // Permission granted by group - print ''; - print ''; + } elseif (isset($permsgroupbyentitypluszero) && is_array($permsgroupbyentitypluszero)) { + print ''; + if (in_array($obj->id, $permsgroupbyentitypluszero)) { // Permission granted by group + print ''; + print ''; + } else { + // Do not own permission + if ($caneditperms) { + print ''; + } else { + print ''; + } + print ''; + } } else { // Do not own permission + print ''; if ($caneditperms) { print ''; } - } else { - // Do not own permission - print ''; - if ($caneditperms) { - print ''; + + // Description of permission (1 or 2 columns) + if (!$user->admin) { + print ''; + + // Permission id + if ($user->admin) { + print ''; } - print ''; - } - // Description of permission (1 or 2 columns) - if (!$user->admin) { - print ''."\n"; - print $permlabel; - $idtouse = $obj->id; - if (in_array($idtouse, array(121, 122, 125, 126))) { // Force message for the 3 permission on third parties - $idtouse = 122; + $i++; } - if ($langs->trans("Permission".$idtouse.'b') != "Permission".$idtouse.'b') { - print '
'.$langs->trans("Permission".$idtouse.'b').''; - } - if ($langs->trans("Permission".$obj->id.'c') != "Permission".$obj->id.'c') { - print '
'.$langs->trans("Permission".$obj->id.'c').''; - } - if (getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { - if (preg_match('/_advance$/', $obj->perms)) { - print ' ('.$langs->trans("AdvancedModeOnly").')'; - } - } - // Special warning case for the permission "Allow to modify other users password" - if ($obj->module == 'user' && $obj->perms == 'user' && $obj->subperms == 'password') { - if ((!empty($object->admin) && !empty($objMod->rights_admin_allowed)) || - in_array($obj->id, $tokenperms) /* if edited user owns this permissions */ || - (isset($permsgroupbyentitypluszero) && is_array($permsgroupbyentitypluszero) && in_array($obj->id, $permsgroupbyentitypluszero))) { - print ' '.img_warning($langs->trans("AllowPasswordResetBySendingANewPassByEmail")); - } - } - // Special warning case for the permission "Create/modify other users, groups and permissions" - if ($obj->module == 'user' && $obj->perms == 'user' && ($obj->subperms == 'creer' || $obj->subperms == 'create')) { - if ((!empty($object->admin) && !empty($objMod->rights_admin_allowed)) || - in_array($obj->id, $tokenperms) /* if edited user owns this permissions */ || - (isset($permsgroupbyentitypluszero) && is_array($permsgroupbyentitypluszero) && in_array($obj->id, $permsgroupbyentitypluszero))) { - print ' '.img_warning($langs->trans("AllowAnyPrivileges")); - } - } - // Special case for reading bank account when you have permission to manage Chart of account - if ($obj->module == 'banque' && $obj->perms == 'lire') { - if (isModEnabled("accounting") && $object->hasRight('accounting', 'chartofaccount')) { - print ' '.img_warning($langs->trans("WarningReadBankAlsoAllowedIfUserHasPermission")); - } - } - - print ''; - - // Permission id - if ($user->admin) { - print ''; - } - - print ''."\n"; - - $i++; - } -} else { - dol_print_error($db); -} -print '
'.$langs->trans("Module").''; - print ''.$langs->trans("All").""; - print ' / '; - print ''.$langs->trans("None").""; - print '
'.$langs->trans("Module").''; + print ''.$langs->trans("All").""; + print ' / '; + print ''.$langs->trans("None").""; + print ''; -print ''.img_picto('', 'folder-open', 'class="paddingright"').''.$langs->trans("ExpandAll").''; -print ' | '; -print ''.img_picto('', 'folder', 'class="paddingright"').''.$langs->trans("UndoExpandAll").''; -print '
'; + print ''.img_picto('', 'folder-open', 'class="paddingright"').''.$langs->trans("ExpandAll").''; + print ' | '; + print ''.img_picto('', 'folder', 'class="paddingright"').''.$langs->trans("UndoExpandAll").''; + print '
'; + print ''; + print img_object('', $picto, 'class="pictoobjectwidth paddingright"').' '.$objMod->getName(); + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + print ''; + print ''; + + print '
'; - print ''; - print img_object('', $picto, 'class="pictoobjectwidth paddingright"').' '.$objMod->getName(); - print ''; + print ''; print ''; - print ''; - print ''; + print ''; + print img_picto($langs->trans("AdministratorDesc"), 'star', 'class="paddingleft valignmiddle"'); print ''; + print ''; + print img_picto($langs->trans("Active"), 'switch_on', '', 0, 0, 0, '', 'opacitymedium'); print ''; + } + print ''; + print ''; + print 'id.'&confirm=yes">'; + //print img_edit_remove($langs->trans("Remove")); + print img_picto($langs->trans("Remove"), 'switch_on'); + print ''; print ''; + print img_picto($langs->trans("Active"), 'switch_on', '', 0, 0, 0, '', 'opacitymedium'); + print ''; - - print ''; - print ''; - - print '
'; - $htmltext = $langs->trans("ID").': '.$obj->id; - $htmltext .= '
'.$langs->trans("Permission").': user->hasRight(\''.dol_escape_htmltag($obj->module).'\', \''.dol_escape_htmltag($obj->perms).'\''.($obj->subperms ? ', \''.dol_escape_htmltag($obj->subperms).'\'' : '').')'; - print $form->textwithpicto('', $htmltext, 1, 'help', 'inline-block marginrightonly'); - //print ''.$obj->id.''; - print '
'; -print '
'; - -print ''; + });'; + print "\n"; -print ''; + // Button expand / collapse all + print '$(".showallperms").on("click", function(){ + console.log("Click on showallperms"); -// TODO : Build the hook management -// Other form for add user to group -//$parameters = array('caneditgroup' => $permissiontoeditgroup, 'groupslist' => $groupslist, 'exclude' => $exclude); -//$reshook = $hookmanager->executeHooks('formAddUserToGroup', $parameters, $object, $action); // Note that $action and $object may have been modified by hook -//print $hookmanager->resPrint; + console.log("delete cookie DOLUSER_PERMS_HIDE_GRP from showallperms click"); + document.cookie = "DOLUSER_PERMS_HIDE_GRP=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/ "; + $(".tdforbreakperms").each( function(){ + moduletohide = $(this).data("hide-perms"); + //console.log(moduletohide); + if ($("#idforbreakperms_"+moduletohide).val() != 0) { + $(this).trigger("click"); // emulate the click, so the cooki will be resaved + } + }) + }); -include_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php'; -print dolJSToSetRandomPassword('api_key', 'generate_api_key', 1); + $(".hideallperms").on("click", function(){ + console.log("Click on hideallperms"); + + $(".tdforbreakperms").each( function(){ + moduletohide = $(this).data("hide-perms"); + //console.log(moduletohide); + if ($("#idforbreakperms_"+moduletohide).val() != 1) { + $(this).trigger("click"); // emulate the click, so the cooki will be resaved + } + }) + });'; + print "\n"; + print ''; + + print ''; + + // TODO : Build the hook management + // Other form for add user to group + //$parameters = array('caneditgroup' => $permissiontoeditgroup, 'groupslist' => $groupslist, 'exclude' => $exclude); + //$reshook = $hookmanager->executeHooks('formAddUserToGroup', $parameters, $object, $action); // Note that $action and $object may have been modified by hook + //print $hookmanager->resPrint; + +} + +if (isModEnabled('api') && $action == 'create') { + include_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php'; + print dolJSToSetRandomPassword('api_key', 'generate_api_key', 1); +} // End of page llxFooter(); diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index 6a3d13ad901..87fe5a33a1c 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -162,7 +162,7 @@ $massactionbutton = $form->selectMassAction('', $arrayofmassactions); $morehtmlright = ''; //if (!empty($moreoptions['showhideaddbutton']) && $conf->use_javascript_ajax) { -$tmpurlforbutton = DOL_URL_ROOT.'/user/api_token/list.php?id='.$id.'&action=create'; +$tmpurlforbutton = DOL_URL_ROOT.'/user/api_token/card.php?id='.$id.'&action=create'; // TODO Permissions ? $morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton, '', $permtoeditline); $morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton); //} From f2bdfcecdb28fe5d43579d3a3f7e111c101ea42c Mon Sep 17 00:00:00 2001 From: yannis Date: Thu, 26 Jun 2025 17:04:42 +0200 Subject: [PATCH 020/387] feat: token list sort/filter/page and improvments --- htdocs/user/api_token/list.php | 285 ++++++++++++++++++++++++++++++--- 1 file changed, 262 insertions(+), 23 deletions(-) diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index 87fe5a33a1c..eb39b09cebd 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -45,16 +45,56 @@ $langs->loadLangs(array('admin', 'users')); // Security check $id = GETPOSTINT('id'); -$action = GETPOST('action', 'aZ09'); -$massaction = GETPOST('massaction', 'alpha'); if (!isset($id) || empty($id)) { accessforbidden(); } // Retrieve needed GETPOSTS for this file +// Action / Massaction +$action = GETPOST('action', 'aZ09'); +$massaction = GETPOST('massaction', 'alpha'); $toselect = GETPOST('toselect', 'array'); +// List filters +$search_token = GETPOST('search_token', 'alpha'); +$search_entity = GETPOST('search_entity', 'alpha'); +$search_datec_startday = GETPOSTINT('search_datec_startday'); +$search_datec_startmonth = GETPOSTINT('search_datec_startmonth'); +$search_datec_startyear = GETPOSTINT('search_datec_startyear'); +$search_datec_endday = GETPOSTINT('search_datec_endday'); +$search_datec_endmonth = GETPOSTINT('search_datec_endmonth'); +$search_datec_endyear = GETPOSTINT('search_datec_endyear'); +$search_datec_start = dol_mktime(0, 0, 0, $search_datec_startmonth, $search_datec_startday, $search_datec_startyear); +$search_datec_end = dol_mktime(23, 59, 59, $search_datec_endmonth, $search_datec_endday, $search_datec_endyear); +$search_tms_startday = GETPOSTINT('search_tms_startday'); +$search_tms_startmonth = GETPOSTINT('search_tms_startmonth'); +$search_tms_startyear = GETPOSTINT('search_tms_startyear'); +$search_tms_endday = GETPOSTINT('search_tms_endday'); +$search_tms_endmonth = GETPOSTINT('search_tms_endmonth'); +$search_tms_endyear = GETPOSTINT('search_tms_endyear'); +$search_tms_start = dol_mktime(0, 0, 0, $search_tms_startmonth, $search_tms_startday, $search_tms_startyear); +$search_tms_end = dol_mktime(23, 59, 59, $search_tms_endmonth, $search_tms_endday, $search_tms_endyear); + +// Pagination +$limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit; +$sortfield = GETPOST('sortfield', 'aZ09comma'); +$sortorder = GETPOST('sortorder', 'aZ09comma'); +$page = GETPOSTISSET('pageplusone') ? (GETPOSTINT('pageplusone') - 1) : GETPOSTINT("page"); +if (empty($page) || $page < 0 || GETPOST('button_search', 'alpha') || GETPOST('button_removefilter', 'alpha')) { + $page = 0; +} +$offset = $limit * $page; +$pageprev = $page - 1; +$pagenext = $page + 1; + +if (!$sortfield) { + $sortfield = 'ot.token'; +} +if (!$sortorder) { + $sortorder = 'DESC'; +} + // $user is current user, $id is id of edited user $canreaduser = ($user->admin || $user->hasRight("user", "user", "read")); $caneditfield = ((($user->id == $id) && $user->hasRight("user", "self", "write")) @@ -72,6 +112,13 @@ if ($user->id != $id && !$canreaduser) { accessforbidden(); } +$arrayfields = array( + 'ot.token' => array('label' => "ApiToken", 'checked' => '1'), + 'ot.entity' => array('label' => "Entity", 'checked' => '1'), + 'ot.datec' => array('label' => "DateCreation", 'checked' => '1'), + 'ot.tms' => array('label' => "DateModification", 'checked' => '1'), +); + $object = new User($db); $object->fetch($id, '', '', 1); $object->loadRights(); @@ -90,6 +137,17 @@ if ($reshook < 0) { } if (empty($reshook)) { + if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers + $search_token = ''; + $search_entity = ''; + $search_datec_start = ''; + $search_datec_end = ''; + $search_tms_start = ''; + $search_tms_end = ''; + + $toselect = array(); + } + if ($action == 'update' && ($caneditfield || !empty($user->admin))) { header('Location: '.$_SERVER["PHP_SELF"].'?id='.$id); exit; @@ -105,8 +163,100 @@ $person_name = !empty($object->firstname) ? $object->lastname.", ".$object->firs $title = $person_name." - ".$langs->trans('Card'); $help_url = ''; +$nbtotalofrecords = ''; +if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { + /* The fast and low memory method to get and count full list converts the sql into a sql count */ + $sqlforcount = 'SELECT COUNT(*) as nbtotalofrecords'; + $sqlforcount .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; + $sqlforcount .= " WHERE entity IN (".$conf->entity.") AND fk_user = ".$id; + $resql = $db->query($sqlforcount); + if ($resql) { + $objforcount = $db->fetch_object($resql); + $nbtotalofrecords = $objforcount->nbtotalofrecords; + } else { + dol_print_error($db); + } + + if (($page * $limit) > $nbtotalofrecords) { // if total resultset is smaller then paging size (filtering), goto and load page 0 + $page = 0; + $offset = 0; + } + $db->free($resql); +} + +$sql = "SELECT ot.rowid as token_id, ot.token, ot.entity, ot.state as rights, ot.datec as date_creation, ot.tms as date_modification"; +$sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; +$sql .= " WHERE ot.fk_user = ".((int) $object->id)." AND entity IN (".$conf->entity.")"; +if ($search_token) { + $sql .= natural_search('ot.token', $search_token); +} +if ($search_entity) { + $sql .= natural_search('ot.entity', $search_entity); +} +if ($search_datec_start) { + $sql .= " AND ot.datec >= '".$db->idate($search_datec_start)."'"; +} +if ($search_datec_end) { + $sql .= " AND ot.datec <= '".$db->idate($search_datec_end)."'"; +} +if ($search_tms_start) { + $sql .= " AND ot.tms >= '".$db->idate($search_tms_start)."'"; +} +if ($search_tms_end) { + $sql .= " AND ot.tms <= '".$db->idate($search_tms_end)."'"; +} +$sql .= $db->order($sortfield, $sortorder); +if ($limit) { + $sql .= $db->plimit($limit + 1, $offset); +} + +$resql = $db->query($sql); + +$num = $db->num_rows($resql); + llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-user page-card_param_ihm'); +$param = '&id='.$id; // We always need the id of the user +if ($limit > 0 && $limit != $conf->liste_limit) { + $param .= '&limit='.((int) $limit); +} +if ($search_datec_startday) { + $param .= '&search_date_startday='.urlencode((string) ($search_datec_startday)); +} +if ($search_datec_startmonth) { + $param .= '&search_date_startmonth='.urlencode((string) ($search_datec_startmonth)); +} +if ($search_datec_startyear) { + $param .= '&search_date_startyear='.urlencode((string) ($search_datec_startyear)); +} +if ($search_datec_endday) { + $param .= '&search_date_endday='.urlencode((string) ($search_datec_endday)); +} +if ($search_datec_endmonth) { + $param .= '&search_date_endmonth='.urlencode((string) ($search_datec_endmonth)); +} +if ($search_datec_endyear) { + $param .= '&search_date_endyear='.urlencode((string) ($search_datec_endyear)); +} +if ($search_tms_startday) { + $param .= '&search_date_startday='.urlencode((string) ($search_tms_startday)); +} +if ($search_tms_startmonth) { + $param .= '&search_date_startmonth='.urlencode((string) ($search_tms_startmonth)); +} +if ($search_tms_startyear) { + $param .= '&search_date_startyear='.urlencode((string) ($search_tms_startyear)); +} +if ($search_tms_endday) { + $param .= '&search_date_endday='.urlencode((string) ($search_tms_endday)); +} +if ($search_tms_endmonth) { + $param .= '&search_date_endmonth='.urlencode((string) ($search_tms_endmonth)); +} +if ($search_tms_endyear) { + $param .= '&search_date_endyear='.urlencode((string) ($search_tms_endyear)); +} + $arrayofselected = is_array($toselect) ? $toselect : array(); $head = user_prepare_head($object); @@ -167,10 +317,14 @@ $tmpurlforbutton = DOL_URL_ROOT.'/user/api_token/card.php?id='.$id.'&action=crea $morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton); //} -print '
'."\n"; +print ''."\n"; print ''; -print load_fiche_titre($langs->trans("ListOfTokensForUser"), $morehtmlright, '', 0, '', '', $massactionbutton); -print '
'; +print ''; +print ''; +print ''; +print ''; + +print_barre_liste($langs->trans("ListOfTokensForUser"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, '', 0, '', '', $limit, 0, 0, 1); // TODO : Build the hook management // Other form for add user to group @@ -183,29 +337,102 @@ if (empty($reshook)) { print ''; print ''; + print ''; + + // Action buttons + if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } + + // Token string + if (!empty($arrayfields['ot.token']['checked'])) { + print ''; + } + + // Entity + if (!empty($arrayfields['ot.entity']['checked'])) { + print ''; + } + + // Number of perms + // We don't search out number of perms because it is a string field, + // and we don't want to count into it with sql query + print ''; + + // Date creation + if (!empty($arrayfields['ot.datec']['checked'])) { + print ''; + } + + // Date modification + if (!empty($arrayfields['ot.tms']['checked'])) { + print ''; + } + + // Action buttons + if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } + + print ""; + print ''; if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { print ''; } - print ''; - print ''; - print ''; - print ''; - print ''; + if (!empty($arrayfields['ot.token']['checked'])) { + print_liste_field_titre($arrayfields['ot.token']['label'], $_SERVER["PHP_SELF"], 'ot.token', '', $param, '', $sortfield, $sortorder); + } + if (!empty($arrayfields['ot.entity']['checked'])) { + print_liste_field_titre($arrayfields['ot.entity']['label'], $_SERVER["PHP_SELF"], 'ot.entity', '', $param, '', $sortfield, $sortorder); + } + print ''; + if (!empty($arrayfields['ot.datec']['checked'])) { + print_liste_field_titre($arrayfields['ot.datec']['label'], $_SERVER["PHP_SELF"], 'ot.datec', '', $param, '', $sortfield, $sortorder, 'center '); + } + if (!empty($arrayfields['ot.tms']['checked'])) { + print_liste_field_titre($arrayfields['ot.tms']['label'], $_SERVER["PHP_SELF"], 'ot.tms', '', $param, '', $sortfield, $sortorder, 'center '); + } + if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } print ''; - $sql = "SELECT ot.rowid as token_id, ot.token, ot.entity, ot.state as rights, ot.datec as date_creation, ot.tms as date_modification"; - $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; - $sql .= " WHERE ot.fk_user = ".((int) $object->id); - - $resql = $db->query($sql); - - // List of groups of user - if ($db->num_rows($resql) > 0) { - while ($obj = $db->fetch_object($resql)) { + // List of tokens of user + $i = 0; + $imaxinloop = ($limit ? min($num, $limit) : $num); + if ($num > 0) { + while ($i < $imaxinloop) { // Compute number of perms + $obj = $db->fetch_object($resql); $numperms = 0; if (!empty($obj->rights)) { $numperms = count(explode(",", $obj->rights)); @@ -231,23 +458,35 @@ if (empty($reshook)) { print ''; - print ''; - print ''; - print ''; + if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } print ''; + $i ++; } } else { print ''; } print "
'; + $searchpicto = $form->showFilterButtons('left'); + print $searchpicto; + print ''; + print ''; + print ''; + print ' 0 ? " disabled" : "").'>'; + print ''; + print '
'; + print $form->selectDate($search_datec_start ? $search_datec_start : -1, 'search_datec_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); + print '
'; + print '
'; + print $form->selectDate($search_datec_end ? $search_datec_end : -1, 'search_datec_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('to')); + print '
'; + print '
'; + print '
'; + print $form->selectDate($search_tms_start ? $search_tms_start : -1, 'search_tms_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); + print '
'; + print '
'; + print $form->selectDate($search_tms_end ? $search_tms_end : -1, 'search_tms_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('to')); + print '
'; + print '
'; + $searchpicto = $form->showFilterButtons('left'); + print $searchpicto; + print '
'; print $form->showCheckAddButtons('checkforselect', 1); print ''.$langs->trans("ApiToken").''.$langs->trans("Entity").''.$langs->trans("NumberOfPermissions").''.$langs->trans("DateCreation").''.$langs->trans("DateModification").''.$langs->trans("NumberOfPermissions").''; + print $form->showCheckAddButtons('checkforselect', 1); + print '
'; print $obj->entity; print ''; + print ''; print $numperms; print ''; + print ''; print $obj->date_creation; print ''; + print ''; print $obj->date_modification; print ''; + if ($massactionbutton || $massaction) { + $selected = 0; + if (in_array($obj->token_id, $arrayofselected)) { + $selected = 1; + } + print ''; + } + print '
'.$langs->trans("None").'
"; - print "
"; + print ''; } // End of page From dcf300b4f8591fe275ff0c489b17c94340046df0 Mon Sep 17 00:00:00 2001 From: yannis Date: Fri, 27 Jun 2025 09:38:28 +0200 Subject: [PATCH 021/387] feat: multicompany managing and list improve display --- htdocs/user/api_token/card.php | 12 +++++++----- htdocs/user/api_token/list.php | 22 ++++++++++++++-------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index b2ae328c246..64faf0f38d2 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -382,11 +382,13 @@ if ($action == 'create') { print ''."\n"; // Entity - print ''.$langs->trans("Entity").''; - print ''; - print $token->entity; - print ''; - print ''."\n"; + if (isModEnabled('multicompany')) { + print ''.$langs->trans("Entity").''; + print ''; + print $token->entity; + print ''; + print ''."\n"; + } // Creation date print ''.$langs->trans("DateCreation").''; diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index eb39b09cebd..c50d676fd06 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -355,7 +355,7 @@ if (empty($reshook)) { } // Entity - if (!empty($arrayfields['ot.entity']['checked'])) { + if (!empty($arrayfields['ot.entity']['checked']) && isModEnabled('multicompany')) { print ''; print ' 0 ? " disabled" : "").'>'; print ''; @@ -364,7 +364,7 @@ if (empty($reshook)) { // Number of perms // We don't search out number of perms because it is a string field, // and we don't want to count into it with sql query - print ''; + print ''; // Date creation if (!empty($arrayfields['ot.datec']['checked'])) { @@ -409,7 +409,7 @@ if (empty($reshook)) { if (!empty($arrayfields['ot.token']['checked'])) { print_liste_field_titre($arrayfields['ot.token']['label'], $_SERVER["PHP_SELF"], 'ot.token', '', $param, '', $sortfield, $sortorder); } - if (!empty($arrayfields['ot.entity']['checked'])) { + if (!empty($arrayfields['ot.entity']['checked']) && isModEnabled('multicompany')) { print_liste_field_titre($arrayfields['ot.entity']['label'], $_SERVER["PHP_SELF"], 'ot.entity', '', $param, '', $sortfield, $sortorder); } print ''.$langs->trans("NumberOfPermissions").''; @@ -455,9 +455,11 @@ if (empty($reshook)) { print $obj->token; print ''; print ''; - print ''; - print $obj->entity; - print ''; + if (isModEnabled('multicompany')) { + print ''; + print $obj->entity; + print ''; + } print ''; print $numperms; print ''; @@ -479,10 +481,14 @@ if (empty($reshook)) { print ''; } print ''; - $i ++; + $i++; } } else { - print ''.$langs->trans("None").''; + $colspan = 5; // Base colspan + if (isModEnabled('multicompany')) { + $colspan++; + } + print ''.$langs->trans("None").''; } print ""; From 7f3b8abdc8c551d400238213fcd2f2e245d24e84 Mon Sep 17 00:00:00 2001 From: yannis Date: Fri, 27 Jun 2025 09:58:29 +0200 Subject: [PATCH 022/387] feat: show back missing add button --- htdocs/user/api_token/list.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index c50d676fd06..e67156a825f 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -324,7 +324,7 @@ print ''; print ''; print ''; -print_barre_liste($langs->trans("ListOfTokensForUser"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, '', 0, '', '', $limit, 0, 0, 1); +print_barre_liste($langs->trans("ListOfTokensForUser"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, '', 0, $morehtmlright, '', $limit, 0, 0, 1); // TODO : Build the hook management // Other form for add user to group From d3ac2ed53b0a8ab5b285c9fb7a47fab72e5698e5 Mon Sep 17 00:00:00 2001 From: yannis Date: Fri, 27 Jun 2025 10:21:08 +0200 Subject: [PATCH 023/387] feat: manage display of massaction buttons --- htdocs/user/api_token/list.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index e67156a825f..b6d22fd67b6 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -54,6 +54,7 @@ if (!isset($id) || empty($id)) { // Action / Massaction $action = GETPOST('action', 'aZ09'); $massaction = GETPOST('massaction', 'alpha'); +$confirm = GETPOST('confirm', 'alpha'); $toselect = GETPOST('toselect', 'array'); // List filters @@ -147,6 +148,10 @@ if (empty($reshook)) { $toselect = array(); } + if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha') + || GETPOST('button_search_x', 'alpha') || GETPOST('button_search.x', 'alpha') || GETPOST('button_search', 'alpha')) { + $massaction = ''; // Protection to avoid mass action if we force a new search during a mass action confirmation + } if ($action == 'update' && ($caneditfield || !empty($user->admin))) { header('Location: '.$_SERVER["PHP_SELF"].'?id='.$id); @@ -308,6 +313,9 @@ print dol_get_fiche_end(); print ''."\n"; $arrayofmassactions['predelete'] = img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete"); +if (GETPOSTINT('nomassaction') || in_array($massaction, array('presend', 'predelete'))) { + $arrayofmassactions = array(); +} $massactionbutton = $form->selectMassAction('', $arrayofmassactions); $morehtmlright = ''; From dc01e804fa4161611e98bb22e55660b16b418daa Mon Sep 17 00:00:00 2001 From: yannis Date: Fri, 27 Jun 2025 15:46:01 +0200 Subject: [PATCH 024/387] feat: update fields and date format display --- htdocs/user/api_token/card.php | 16 ++++++++++++---- htdocs/user/api_token/list.php | 4 ++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 64faf0f38d2..2cb7ce61873 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -208,7 +208,7 @@ if (empty($reshook)) { dol_print_error($db); } - $token->rights = $newrigths; + $reloadtoken = true; } if ($action == 'delrights' && $caneditperms && $confirm == 'yes') { @@ -232,7 +232,7 @@ if (empty($reshook)) { dol_print_error($db); } - $token->rights = $newrigths; + $reloadtoken = true; } if ($action == 'add') { @@ -277,6 +277,14 @@ if (empty($reshook)) { } } +if (isset($reloadtoken)) { // If we add or del rights, we want to refresh the token with its new updated fields + $sql = "SELECT ot.rowid as token_id, ot.token, ot.entity, ot.state as rights, ot.datec as date_creation, ot.tms as date_modification"; + $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; + $sql .= " WHERE ot.rowid = ".((int) $tokenid); + + $resql = $db->query($sql); + $token = $db->fetch_object($resql); +} /* * View @@ -393,14 +401,14 @@ if ($action == 'create') { // Creation date print ''.$langs->trans("DateCreation").''; print ''; - print $token->date_creation; + print dol_print_date($db->jdate($token->date_creation), 'day'); print ''; print ''."\n"; // Modification date print ''.$langs->trans("DateModification").''; print ''; - print $token->date_modification; + print dol_print_date($db->jdate($token->date_modification), 'day'); print ''; print ''."\n"; diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index b6d22fd67b6..1ea0ad969fe 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -472,10 +472,10 @@ if (empty($reshook)) { print $numperms; print ''; print ''; - print $obj->date_creation; + print dol_print_date($db->jdate($obj->date_creation), 'day'); print ''; print ''; - print $obj->date_modification; + print dol_print_date($db->jdate($obj->date_modification), 'day'); print ''; if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { print ''; From 61e24d82fc79fd0e88b1e76cbf0a7035caf0c28f Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 30 Jun 2025 16:49:52 +0200 Subject: [PATCH 025/387] feat: query to import old user token to new implement with perms --- .../install/mysql/migration/22.0.0-23.0.0.sql | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 htdocs/install/mysql/migration/22.0.0-23.0.0.sql diff --git a/htdocs/install/mysql/migration/22.0.0-23.0.0.sql b/htdocs/install/mysql/migration/22.0.0-23.0.0.sql new file mode 100644 index 00000000000..f83e3775ef7 --- /dev/null +++ b/htdocs/install/mysql/migration/22.0.0-23.0.0.sql @@ -0,0 +1,35 @@ +-- +-- This file is executed by calling /install/index.php page +-- when current version is higher than the name of this file. +-- Be carefull in the position of each SQL request. +-- +-- To restrict request to Mysql version x.y minimum use -- VMYSQLx.y +-- To restrict request to Pgsql version x.y minimum use -- VPGSQLx.y +-- To rename a table: ALTER TABLE llx_table RENAME TO llx_table_new; -- Note that "RENAME TO" is both compatible mysql/postgesql, not "RENAME" alone. +-- To add a column: ALTER TABLE llx_table ADD COLUMN newcol varchar(60) NOT NULL DEFAULT '0' AFTER existingcol; +-- To rename a column: ALTER TABLE llx_table CHANGE COLUMN oldname newname varchar(60); +-- To drop a column: ALTER TABLE llx_table DROP COLUMN oldname; +-- To change type of field: ALTER TABLE llx_table MODIFY COLUMN name varchar(60); +-- To drop a foreign key or constraint: ALTER TABLE llx_table DROP FOREIGN KEY fk_name; +-- To create a unique index: ALTER TABLE llx_table ADD UNIQUE INDEX uk_table_field (field); +-- To drop an index: -- VMYSQL4.1 DROP INDEX nomindex ON llx_table; +-- To drop an index: -- VPGSQL8.2 DROP INDEX nomindex; +-- To make pk to be auto increment (mysql): +-- -- VMYSQL4.3 ALTER TABLE llx_table ADD PRIMARY KEY(rowid); +-- -- VMYSQL4.3 ALTER TABLE llx_table CHANGE COLUMN rowid rowid INTEGER NOT NULL AUTO_INCREMENT; +-- To make pk to be auto increment (postgres): +-- -- VPGSQL8.2 CREATE SEQUENCE llx_table_rowid_seq OWNED BY llx_table.rowid; +-- -- VPGSQL8.2 ALTER TABLE llx_table ADD PRIMARY KEY (rowid); +-- -- VPGSQL8.2 ALTER TABLE llx_table ALTER COLUMN rowid SET DEFAULT nextval('llx_table_rowid_seq'); +-- -- VPGSQL8.2 SELECT setval('llx_table_rowid_seq', MAX(rowid)) FROM llx_table; +-- To set a field as NULL: -- VMYSQL4.3 ALTER TABLE llx_table MODIFY COLUMN name varchar(60) NULL; +-- To set a field as NULL: -- VPGSQL8.2 ALTER TABLE llx_table ALTER COLUMN name DROP NOT NULL; +-- To set a field as NOT NULL: -- VMYSQL4.3 ALTER TABLE llx_table MODIFY COLUMN name varchar(60) NOT NULL; +-- To set a field as NOT NULL: -- VPGSQL8.2 ALTER TABLE llx_table ALTER COLUMN name SET NOT NULL; +-- To set a field as default NULL: -- VPGSQL8.2 ALTER TABLE llx_table ALTER COLUMN name SET DEFAULT NULL; +-- Note: fields with type BLOB/TEXT can't have default value. +-- To rebuild sequence for postgresql after insert, by forcing id autoincrement fields: +-- -- VPGSQL8.2 SELECT dol_util_rebuild_sequences(); + + +INSERT INTO llx_oauth_token (service, token, state, fk_user, datec, entity) SELECT 'dolibarr_rest_api' AS service, u.api_key AS token, GROUP_CONCAT(rights.fk_id SEPARATOR ',') AS state, u.rowid AS fk_user, CURRENT_TIMESTAMP AS datec, rights.entity FROM llx_user AS u LEFT JOIN (SELECT fk_user, fk_id, entity FROM llx_user_rights UNION SELECT gu.fk_user, gr.fk_id, gr.entity FROM llx_usergroup_user AS gu JOIN llx_usergroup_rights AS gr on gu.fk_usergroup = gr.fk_usergroup) AS rights on rights.fk_user = u.rowid WHERE u.api_key IS NOT NULL GROUP BY u.login, u.api_key, u.rowid, rights.entity; From deea6c174ee59f4fd06fb9e83c6418e7a5bf1305 Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 30 Jun 2025 17:32:13 +0200 Subject: [PATCH 026/387] feat: check if duplicate token key --- htdocs/user/api_token/card.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 2cb7ce61873..0e5ee99b7e3 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -41,7 +41,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php'; */ // Load translation files required by page -$langs->loadLangs(array('admin', 'users')); +$langs->loadLangs(array('admin', 'users', 'errors')); $error = 0; // Security check @@ -244,6 +244,23 @@ if (empty($reshook)) { $error++; } + $sqlforcount = 'SELECT COUNT(*) as nbtotalofrecords'; + $sqlforcount .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; + $sqlforcount .= " WHERE token = '".$db->escape($tokenstring)."'"; + $sqlforcount .= " AND service = 'dolibarr_rest_api'"; + $resql = $db->query($sqlforcount); + if ($resql) { + $objforcount = $db->fetch_object($resql); + $nbtotalofrecords = $objforcount->nbtotalofrecords; + } else { + dol_print_error($db); + } + if ($nbtotalofrecords > 0) { + setEventMessages($langs->trans("ErrorFieldExist", $langs->transnoentitiesnoconv("ApiToken")), null, 'errors'); + $action = 'create'; + $error++; + } + if (!$error) { $db->begin(); From 3d9a7fa894e79a623ab9aa4a5befa072e5dd8c3d Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 1 Jul 2025 09:33:12 +0200 Subject: [PATCH 027/387] feat: tokens list massaction delete --- htdocs/user/api_token/list.php | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index 1ea0ad969fe..c1049fb3133 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -42,6 +42,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php'; // Load translation files required by page $langs->loadLangs(array('admin', 'users')); +$error = 0; // Security check $id = GETPOSTINT('id'); @@ -100,6 +101,7 @@ if (!$sortorder) { $canreaduser = ($user->admin || $user->hasRight("user", "user", "read")); $caneditfield = ((($user->id == $id) && $user->hasRight("user", "self", "write")) || (($user->id != $id) && $user->hasRight("user", "user", "write"))); +$permissiontodelete = ($user->admin || $caneditfield); // Security check $socid = 0; @@ -157,6 +159,47 @@ if (empty($reshook)) { header('Location: '.$_SERVER["PHP_SELF"].'?id='.$id); exit; } + + if (($action == 'delete' && $confirm == 'yes') && $permissiontodelete) { + $db->begin(); + + $nbok = 0; + $TMsg = array(); + + //$toselect could contain duplicate entries, cf https://github.com/Dolibarr/dolibarr/issues/26244 + $unique_arr = array_unique($toselect); + foreach ($unique_arr as $toselectid) { + $sql = "DELETE FROM ".MAIN_DB_PREFIX."oauth_token"; + $sql .= " WHERE rowid = '".$toselectid."'"; + $sql .= " AND service = 'dolibarr_rest_api'"; + + $result = $db->query($sql); + + if ($result > 0) { + $nbok++; + } else { + setEventMessages($db->error(), null, 'errors'); + $error++; + break; + } + } + + if (empty($error)) { + // Message for elements well deleted + if ($nbok > 1) { + setEventMessages($langs->trans("RecordsDeleted", $nbok), null, 'mesgs'); + } elseif ($nbok > 0) { + setEventMessages($langs->trans("RecordDeleted", $nbok), null, 'mesgs'); + } else { + setEventMessages($langs->trans("NoRecordDeleted"), null, 'mesgs'); + } + $db->commit(); + } else { + $db->rollback(); + } + + //var_dump($listofobjectthirdparties);exit; + } } @@ -334,6 +377,8 @@ print ''; print_barre_liste($langs->trans("ListOfTokensForUser"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, '', 0, $morehtmlright, '', $limit, 0, 0, 1); +include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php'; + // TODO : Build the hook management // Other form for add user to group //$parameters = array('caneditgroup' => $permissiontoeditgroup, 'groupslist' => $groupslist, 'exclude' => $exclude); From c2ef82645a85ba4256dfdb36cd62d9a8f36d7f4b Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 1 Jul 2025 09:56:24 +0200 Subject: [PATCH 028/387] feat: protection from perm in token that user don't have --- htdocs/user/api_token/card.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 0e5ee99b7e3..5b29bff7582 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -153,6 +153,17 @@ if (empty($reshook)) { } } $sql .= ")"; + // To avoid better perms in token + $sql .= " AND id IN ("; + $sql .= " SELECT ur.fk_id"; + $sql .= " FROM llx_user_rights as ur"; + $sql .= " WHERE ur.entity = ".$token->entity; + $sql .= " AND ur.fk_user = ".$id; + $sql .= " UNION"; + $sql .= " SELECT gr.fk_id"; + $sql .= " FROM llx_usergroup_rights as gr"; + $sql .= " WHERE EXISTS(SELECT gu.rowid FROM llx_usergroup_user as gu WHERE gu.fk_user = ".$id; + $sql .= " AND gu.fk_usergroup = gr.fk_usergroup))"; $resql = $db->query($sql); while ($obj = $db->fetch_object($resql)) { $rigthsarray []= $obj->id; From 833bba723c84a669e103ed213167c2b39346c849 Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 1 Jul 2025 10:06:53 +0200 Subject: [PATCH 029/387] refactor: change table name in sql query for pre-commit --- htdocs/user/api_token/card.php | 14 ++++----- htdocs/user/api_token/list.php | 54 ++++++++++++++++---------------- htdocs/user/class/user.class.php | 6 ++-- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 5b29bff7582..f94c4f9c2bf 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -79,9 +79,9 @@ if ($user->id != $id && !$canreaduser) { accessforbidden(); } -$sql = "SELECT ot.rowid as token_id, ot.token, ot.entity, ot.state as rights, ot.datec as date_creation, ot.tms as date_modification"; -$sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; -$sql .= " WHERE ot.rowid = ".((int) $tokenid); +$sql = "SELECT oat.rowid as token_id, oat.token, oat.entity, oat.state as rights, oat.datec as date_creation, oat.tms as date_modification"; +$sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; +$sql .= " WHERE oat.rowid = ".((int) $tokenid); $resql = $db->query($sql); @@ -256,7 +256,7 @@ if (empty($reshook)) { } $sqlforcount = 'SELECT COUNT(*) as nbtotalofrecords'; - $sqlforcount .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; + $sqlforcount .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; $sqlforcount .= " WHERE token = '".$db->escape($tokenstring)."'"; $sqlforcount .= " AND service = 'dolibarr_rest_api'"; $resql = $db->query($sqlforcount); @@ -306,9 +306,9 @@ if (empty($reshook)) { } if (isset($reloadtoken)) { // If we add or del rights, we want to refresh the token with its new updated fields - $sql = "SELECT ot.rowid as token_id, ot.token, ot.entity, ot.state as rights, ot.datec as date_creation, ot.tms as date_modification"; - $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; - $sql .= " WHERE ot.rowid = ".((int) $tokenid); + $sql = "SELECT oat.rowid as token_id, oat.token, oat.entity, oat.state as rights, oat.datec as date_creation, oat.tms as date_modification"; + $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; + $sql .= " WHERE oat.rowid = ".((int) $tokenid); $resql = $db->query($sql); $token = $db->fetch_object($resql); diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index c1049fb3133..af1dd573719 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -91,7 +91,7 @@ $pageprev = $page - 1; $pagenext = $page + 1; if (!$sortfield) { - $sortfield = 'ot.token'; + $sortfield = 'oat.token'; } if (!$sortorder) { $sortorder = 'DESC'; @@ -116,10 +116,10 @@ if ($user->id != $id && !$canreaduser) { } $arrayfields = array( - 'ot.token' => array('label' => "ApiToken", 'checked' => '1'), - 'ot.entity' => array('label' => "Entity", 'checked' => '1'), - 'ot.datec' => array('label' => "DateCreation", 'checked' => '1'), - 'ot.tms' => array('label' => "DateModification", 'checked' => '1'), + 'oat.token' => array('label' => "ApiToken", 'checked' => '1'), + 'oat.entity' => array('label' => "Entity", 'checked' => '1'), + 'oat.datec' => array('label' => "DateCreation", 'checked' => '1'), + 'oat.tms' => array('label' => "DateModification", 'checked' => '1'), ); $object = new User($db); @@ -215,7 +215,7 @@ $nbtotalofrecords = ''; if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { /* The fast and low memory method to get and count full list converts the sql into a sql count */ $sqlforcount = 'SELECT COUNT(*) as nbtotalofrecords'; - $sqlforcount .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; + $sqlforcount .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; $sqlforcount .= " WHERE entity IN (".$conf->entity.") AND fk_user = ".$id; $resql = $db->query($sqlforcount); if ($resql) { @@ -232,26 +232,26 @@ if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { $db->free($resql); } -$sql = "SELECT ot.rowid as token_id, ot.token, ot.entity, ot.state as rights, ot.datec as date_creation, ot.tms as date_modification"; -$sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; -$sql .= " WHERE ot.fk_user = ".((int) $object->id)." AND entity IN (".$conf->entity.")"; +$sql = "SELECT oat.rowid as token_id, oat.token, oat.entity, oat.state as rights, oat.datec as date_creation, oat.tms as date_modification"; +$sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; +$sql .= " WHERE oat.fk_user = ".((int) $object->id)." AND entity IN (".$conf->entity.")"; if ($search_token) { - $sql .= natural_search('ot.token', $search_token); + $sql .= natural_search('oat.token', $search_token); } if ($search_entity) { - $sql .= natural_search('ot.entity', $search_entity); + $sql .= natural_search('oat.entity', $search_entity); } if ($search_datec_start) { - $sql .= " AND ot.datec >= '".$db->idate($search_datec_start)."'"; + $sql .= " AND oat.datec >= '".$db->idate($search_datec_start)."'"; } if ($search_datec_end) { - $sql .= " AND ot.datec <= '".$db->idate($search_datec_end)."'"; + $sql .= " AND oat.datec <= '".$db->idate($search_datec_end)."'"; } if ($search_tms_start) { - $sql .= " AND ot.tms >= '".$db->idate($search_tms_start)."'"; + $sql .= " AND oat.tms >= '".$db->idate($search_tms_start)."'"; } if ($search_tms_end) { - $sql .= " AND ot.tms <= '".$db->idate($search_tms_end)."'"; + $sql .= " AND oat.tms <= '".$db->idate($search_tms_end)."'"; } $sql .= $db->order($sortfield, $sortorder); if ($limit) { @@ -401,14 +401,14 @@ if (empty($reshook)) { } // Token string - if (!empty($arrayfields['ot.token']['checked'])) { + if (!empty($arrayfields['oat.token']['checked'])) { print ''; print ''; print ''; } // Entity - if (!empty($arrayfields['ot.entity']['checked']) && isModEnabled('multicompany')) { + if (!empty($arrayfields['oat.entity']['checked']) && isModEnabled('multicompany')) { print ''; print ' 0 ? " disabled" : "").'>'; print ''; @@ -420,7 +420,7 @@ if (empty($reshook)) { print ''; // Date creation - if (!empty($arrayfields['ot.datec']['checked'])) { + if (!empty($arrayfields['oat.datec']['checked'])) { print ''; print '
'; print $form->selectDate($search_datec_start ? $search_datec_start : -1, 'search_datec_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); @@ -432,7 +432,7 @@ if (empty($reshook)) { } // Date modification - if (!empty($arrayfields['ot.tms']['checked'])) { + if (!empty($arrayfields['oat.tms']['checked'])) { print ''; print '
'; print $form->selectDate($search_tms_start ? $search_tms_start : -1, 'search_tms_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); @@ -459,18 +459,18 @@ if (empty($reshook)) { print $form->showCheckAddButtons('checkforselect', 1); print ''; } - if (!empty($arrayfields['ot.token']['checked'])) { - print_liste_field_titre($arrayfields['ot.token']['label'], $_SERVER["PHP_SELF"], 'ot.token', '', $param, '', $sortfield, $sortorder); + if (!empty($arrayfields['oat.token']['checked'])) { + print_liste_field_titre($arrayfields['oat.token']['label'], $_SERVER["PHP_SELF"], 'oat.token', '', $param, '', $sortfield, $sortorder); } - if (!empty($arrayfields['ot.entity']['checked']) && isModEnabled('multicompany')) { - print_liste_field_titre($arrayfields['ot.entity']['label'], $_SERVER["PHP_SELF"], 'ot.entity', '', $param, '', $sortfield, $sortorder); + if (!empty($arrayfields['oat.entity']['checked']) && isModEnabled('multicompany')) { + print_liste_field_titre($arrayfields['oat.entity']['label'], $_SERVER["PHP_SELF"], 'oat.entity', '', $param, '', $sortfield, $sortorder); } print ''.$langs->trans("NumberOfPermissions").''; - if (!empty($arrayfields['ot.datec']['checked'])) { - print_liste_field_titre($arrayfields['ot.datec']['label'], $_SERVER["PHP_SELF"], 'ot.datec', '', $param, '', $sortfield, $sortorder, 'center '); + if (!empty($arrayfields['oat.datec']['checked'])) { + print_liste_field_titre($arrayfields['oat.datec']['label'], $_SERVER["PHP_SELF"], 'oat.datec', '', $param, '', $sortfield, $sortorder, 'center '); } - if (!empty($arrayfields['ot.tms']['checked'])) { - print_liste_field_titre($arrayfields['ot.tms']['label'], $_SERVER["PHP_SELF"], 'ot.tms', '', $param, '', $sortfield, $sortorder, 'center '); + if (!empty($arrayfields['oat.tms']['checked'])) { + print_liste_field_titre($arrayfields['oat.tms']['label'], $_SERVER["PHP_SELF"], 'oat.tms', '', $param, '', $sortfield, $sortorder, 'center '); } if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { print ''; diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 89c74b8b2e6..66d7fd152b2 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -1348,9 +1348,9 @@ class User extends CommonObject if (!$alreadyloaded) { if (!empty($token)) { // If token specified, we only load perms from it - $sql = "SELECT ot.state as rights, ot.entity"; - $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as ot"; - $sql .= " WHERE ot.token = '".($this->db->escape($token))."'"; + $sql = "SELECT oat.state as rights, oat.entity"; + $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; + $sql .= " WHERE oat.token = '".($this->db->escape($token))."'"; $resql = $this->db->query($sql); if ($resql) { From 79d8b9a63e294a446d0d7441f5bc998f76432f25 Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 1 Jul 2025 12:03:22 +0200 Subject: [PATCH 030/387] feat: all users token list tab in api admin page --- htdocs/api/admin/index.php | 5 + htdocs/api/admin/token_list.php | 452 ++++++++++++++++++++++++++++++++ htdocs/core/lib/api.lib.php | 50 ++++ htdocs/langs/en_US/users.lang | 2 + 4 files changed, 509 insertions(+) create mode 100644 htdocs/api/admin/token_list.php create mode 100644 htdocs/core/lib/api.lib.php diff --git a/htdocs/api/admin/index.php b/htdocs/api/admin/index.php index 8c3c716b837..84f7952f3a3 100644 --- a/htdocs/api/admin/index.php +++ b/htdocs/api/admin/index.php @@ -31,6 +31,7 @@ require '../../main.inc.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/api.lib.php'; /** * @var Conf $conf @@ -112,6 +113,10 @@ llxHeader('', '', '', '', 0, 0, '', '', '', 'mod-api page-admin-index'); $linkback = ''.$langs->trans("BackToModuleList").''; print load_fiche_titre($langs->trans("ApiSetup"), $linkback, 'title_setup'); +$head = api_admin_prepare_head(); + +print dol_get_fiche_head($head, 'parameter', '', -1); + print ''.$langs->trans("ApiDesc")."
\n"; print "
\n"; diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php new file mode 100644 index 00000000000..a359ede7c82 --- /dev/null +++ b/htdocs/api/admin/token_list.php @@ -0,0 +1,452 @@ + + * Copyright (C) 2005-2016 Laurent Destailleur + * Copyright (C) 2011 Juanjo Menent + * Copyright (C) 2012-2018 Regis Houssin + * Copyright (C) 2015 Jean-François Ferry + * Copyright (C) 2024 MDW + * 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/api/admin/index.php + * \ingroup api + * \brief Page to setup Webservices REST module + */ + +// Load Dolibarr environment +require '../../main.inc.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/api.lib.php'; + +/** + * @var Conf $conf + * @var DoliDB $db + * @var Form $form + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + * + * @var string $dolibarr_main_url_root + */ + +// Load translation files required by the page +$langs->loadLangs(array('admin', 'users')); + +if (!$user->admin) { + accessforbidden(); +} + +// Retrieve needed GETPOSTS for this file +// Action / Massaction +$action = GETPOST('action', 'aZ09'); +$massaction = GETPOST('massaction', 'alpha'); +$confirm = GETPOST('confirm', 'alpha'); +$toselect = GETPOST('toselect', 'array'); + +// List filters +$search_token = GETPOST('search_token', 'alpha'); +$search_user = GETPOST('search_user', 'alpha'); +$search_entity = GETPOST('search_entity', 'alpha'); +$search_datec_startday = GETPOSTINT('search_datec_startday'); +$search_datec_startmonth = GETPOSTINT('search_datec_startmonth'); +$search_datec_startyear = GETPOSTINT('search_datec_startyear'); +$search_datec_endday = GETPOSTINT('search_datec_endday'); +$search_datec_endmonth = GETPOSTINT('search_datec_endmonth'); +$search_datec_endyear = GETPOSTINT('search_datec_endyear'); +$search_datec_start = dol_mktime(0, 0, 0, $search_datec_startmonth, $search_datec_startday, $search_datec_startyear); +$search_datec_end = dol_mktime(23, 59, 59, $search_datec_endmonth, $search_datec_endday, $search_datec_endyear); +$search_tms_startday = GETPOSTINT('search_tms_startday'); +$search_tms_startmonth = GETPOSTINT('search_tms_startmonth'); +$search_tms_startyear = GETPOSTINT('search_tms_startyear'); +$search_tms_endday = GETPOSTINT('search_tms_endday'); +$search_tms_endmonth = GETPOSTINT('search_tms_endmonth'); +$search_tms_endyear = GETPOSTINT('search_tms_endyear'); +$search_tms_start = dol_mktime(0, 0, 0, $search_tms_startmonth, $search_tms_startday, $search_tms_startyear); +$search_tms_end = dol_mktime(23, 59, 59, $search_tms_endmonth, $search_tms_endday, $search_tms_endyear); + +// Pagination +$limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit; +$sortfield = GETPOST('sortfield', 'aZ09comma'); +$sortorder = GETPOST('sortorder', 'aZ09comma'); +$page = GETPOSTISSET('pageplusone') ? (GETPOSTINT('pageplusone') - 1) : GETPOSTINT("page"); +if (empty($page) || $page < 0 || GETPOST('button_search', 'alpha') || GETPOST('button_removefilter', 'alpha')) { + $page = 0; +} +$offset = $limit * $page; +$pageprev = $page - 1; +$pagenext = $page + 1; + +if (!$sortfield) { + $sortfield = 'oat.token'; +} +if (!$sortorder) { + $sortorder = 'DESC'; +} + +$arrayfields = array( + 'oat.token' => array('label' => "ApiToken", 'checked' => '1'), + 'u.login' => array('label' => "User", 'checked' => '1'), + 'oat.entity' => array('label' => "Entity", 'checked' => '1'), + 'oat.datec' => array('label' => "DateCreation", 'checked' => '1'), + 'oat.tms' => array('label' => "DateModification", 'checked' => '1'), +); + +/* + * Action + */ +if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers + $search_token = ''; + $search_user = ''; + $search_entity = ''; + $search_datec_startday = ''; + $search_datec_startmonth = ''; + $search_datec_startyear = ''; + $search_datec_endday = ''; + $search_datec_endmonth = ''; + $search_datec_endyear = ''; + $search_datec_start = ''; + $search_datec_end = ''; + $search_tms_startday = ''; + $search_tms_startmonth = ''; + $search_tms_startyear = ''; + $search_tms_endday = ''; + $search_tms_endmonth = ''; + $search_tms_endyear = ''; + $search_tms_start = ''; + $search_tms_end = ''; + + $toselect = array(); +} +if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha') + || GETPOST('button_search_x', 'alpha') || GETPOST('button_search.x', 'alpha') || GETPOST('button_search', 'alpha')) { + $massaction = ''; // Protection to avoid mass action if we force a new search during a mass action confirmation +} + +/* + * View + */ + +$nbtotalofrecords = ''; +if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { + /* The fast and low memory method to get and count full list converts the sql into a sql count */ + $sqlforcount = 'SELECT COUNT(*) as nbtotalofrecords'; + $sqlforcount .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; + $sqlforcount .= " WHERE entity IN (".$conf->entity.")"; + $sqlforcount .= " AND service = 'dolibarr_rest_api'"; + $resql = $db->query($sqlforcount); + if ($resql) { + $objforcount = $db->fetch_object($resql); + $nbtotalofrecords = $objforcount->nbtotalofrecords; + } else { + dol_print_error($db); + } + + if (($page * $limit) > $nbtotalofrecords) { // if total resultset is smaller then paging size (filtering), goto and load page 0 + $page = 0; + $offset = 0; + } + $db->free($resql); +} + +$sql = "SELECT oat.rowid as token_id, oat.token, oat.entity, oat.state as rights, oat.fk_user as user_id, u.login as user, oat.datec as date_creation, oat.tms as date_modification"; +$sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; +$sql .= " JOIN llx_user as u ON u.rowid = oat.fk_user"; +$sql .= " WHERE oat.entity IN (".$conf->entity.")"; +$sql .= " AND service = 'dolibarr_rest_api'"; +if ($search_token) { + $sql .= natural_search('oat.token', $search_token); +} +if ($search_user) { + $sql .= natural_search('u.login', $search_user); +} +if ($search_entity) { + $sql .= natural_search('oat.entity', $search_entity); +} +if ($search_datec_start) { + $sql .= " AND oat.datec >= '".$db->idate($search_datec_start)."'"; +} +if ($search_datec_end) { + $sql .= " AND oat.datec <= '".$db->idate($search_datec_end)."'"; +} +if ($search_tms_start) { + $sql .= " AND oat.tms >= '".$db->idate($search_tms_start)."'"; +} +if ($search_tms_end) { + $sql .= " AND oat.tms <= '".$db->idate($search_tms_end)."'"; +} +$sql .= $db->order($sortfield, $sortorder); +if ($limit) { + $sql .= $db->plimit($limit + 1, $offset); +} + +$resql = $db->query($sql); + +$num = $db->num_rows($resql); + +llxHeader('', '', '', '', 0, 0, '', '', '', 'mod-api page-admin-index'); + +$param = ''; +if ($limit > 0 && $limit != $conf->liste_limit) { + $param .= '&limit='.((int) $limit); +} +if ($search_datec_startday) { + $param .= '&search_date_startday='.urlencode((string) ($search_datec_startday)); +} +if ($search_datec_startmonth) { + $param .= '&search_date_startmonth='.urlencode((string) ($search_datec_startmonth)); +} +if ($search_datec_startyear) { + $param .= '&search_date_startyear='.urlencode((string) ($search_datec_startyear)); +} +if ($search_datec_endday) { + $param .= '&search_date_endday='.urlencode((string) ($search_datec_endday)); +} +if ($search_datec_endmonth) { + $param .= '&search_date_endmonth='.urlencode((string) ($search_datec_endmonth)); +} +if ($search_datec_endyear) { + $param .= '&search_date_endyear='.urlencode((string) ($search_datec_endyear)); +} +if ($search_tms_startday) { + $param .= '&search_date_startday='.urlencode((string) ($search_tms_startday)); +} +if ($search_tms_startmonth) { + $param .= '&search_date_startmonth='.urlencode((string) ($search_tms_startmonth)); +} +if ($search_tms_startyear) { + $param .= '&search_date_startyear='.urlencode((string) ($search_tms_startyear)); +} +if ($search_tms_endday) { + $param .= '&search_date_endday='.urlencode((string) ($search_tms_endday)); +} +if ($search_tms_endmonth) { + $param .= '&search_date_endmonth='.urlencode((string) ($search_tms_endmonth)); +} +if ($search_tms_endyear) { + $param .= '&search_date_endyear='.urlencode((string) ($search_tms_endyear)); +} + +$arrayofselected = is_array($toselect) ? $toselect : array(); + +$linkback = ''.$langs->trans("BackToModuleList").''; +print load_fiche_titre($langs->trans("ApiSetup"), $linkback, 'title_setup'); + +$head = api_admin_prepare_head(); + +print dol_get_fiche_head($head, 'token_list', '', -1); + +$arrayofmassactions['predelete'] = img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete"); +if (GETPOSTINT('nomassaction') || in_array($massaction, array('presend', 'predelete'))) { + $arrayofmassactions = array(); +} +$massactionbutton = $form->selectMassAction('', $arrayofmassactions); + +$morehtmlright = ''; +$tmpurlforbutton = DOL_URL_ROOT.'/user/api_token/card.php?action=create'; +$morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton); + +print '
'; +print ''; +print ''; +print ''; +print ''; + +print_barre_liste($langs->trans("ListOfTokensForAllUsers"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, '', 0, $morehtmlright, '', $limit, 0, 0, 1); + +include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php'; + +if (empty($reshook)) { + + print ''; + print ''; + + print ''; + + // Action buttons + if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } + + // Token string + if (!empty($arrayfields['oat.token']['checked'])) { + print ''; + } + + // Entity + if (!empty($arrayfields['u.login']['checked'])) { + print ''; + } + + // Entity + if (!empty($arrayfields['oat.entity']['checked']) && isModEnabled('multicompany')) { + print ''; + } + + // Number of perms + // We don't search out number of perms because it is a string field, + // and we don't want to count into it with sql query + print ''; + + // Date creation + if (!empty($arrayfields['oat.datec']['checked'])) { + print ''; + } + + // Date modification + if (!empty($arrayfields['oat.tms']['checked'])) { + print ''; + } + + // Action buttons + if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } + + print ""; + + print ''; + if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } + if (!empty($arrayfields['oat.token']['checked'])) { + print_liste_field_titre($arrayfields['oat.token']['label'], $_SERVER["PHP_SELF"], 'oat.token', '', $param, '', $sortfield, $sortorder); + } + if (!empty($arrayfields['u.login']['checked'])) { + print_liste_field_titre($arrayfields['u.login']['label'], $_SERVER["PHP_SELF"], 'u.login', '', $param, '', $sortfield, $sortorder); + } + if (!empty($arrayfields['oat.entity']['checked']) && isModEnabled('multicompany')) { + print_liste_field_titre($arrayfields['oat.entity']['label'], $_SERVER["PHP_SELF"], 'oat.entity', '', $param, '', $sortfield, $sortorder); + } + print ''; + if (!empty($arrayfields['oat.datec']['checked'])) { + print_liste_field_titre($arrayfields['oat.datec']['label'], $_SERVER["PHP_SELF"], 'oat.datec', '', $param, '', $sortfield, $sortorder, 'center '); + } + if (!empty($arrayfields['oat.tms']['checked'])) { + print_liste_field_titre($arrayfields['oat.tms']['label'], $_SERVER["PHP_SELF"], 'oat.tms', '', $param, '', $sortfield, $sortorder, 'center '); + } + if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } + print ''; + + // List of tokens of user + $i = 0; + $imaxinloop = ($limit ? min($num, $limit) : $num); + if ($num > 0) { + while ($i < $imaxinloop) { + // Compute number of perms + $obj = $db->fetch_object($resql); + $numperms = 0; + if (!empty($obj->rights)) { + $numperms = count(explode(",", $obj->rights)); + } + print ''; + // Action column + if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } + print ''; + print ''; + if (isModEnabled('multicompany')) { + print ''; + } + print ''; + print ''; + print ''; + if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } + print ''; + $i++; + } + } else { + $colspan = 6; // Base colspan + if (isModEnabled('multicompany')) { + $colspan++; + } + print ''; + } + + print "
'; + $searchpicto = $form->showFilterButtons('left'); + print $searchpicto; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print '
'; + print $form->selectDate($search_datec_start ? $search_datec_start : -1, 'search_datec_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); + print '
'; + print '
'; + print $form->selectDate($search_datec_end ? $search_datec_end : -1, 'search_datec_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('to')); + print '
'; + print '
'; + print '
'; + print $form->selectDate($search_tms_start ? $search_tms_start : -1, 'search_tms_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); + print '
'; + print '
'; + print $form->selectDate($search_tms_end ? $search_tms_end : -1, 'search_tms_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('to')); + print '
'; + print '
'; + $searchpicto = $form->showFilterButtons('left'); + print $searchpicto; + print '
'; + print $form->showCheckAddButtons('checkforselect', 1); + print ''.$langs->trans("NumberOfPermissions").''; + print $form->showCheckAddButtons('checkforselect', 1); + print '
'; + if ($massactionbutton || $massaction) { // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined + $selected = 0; + if (in_array($obj->token_id, $arrayofselected)) { + $selected = 1; + } + print ''; + } + print ''; + print ''; + print $obj->token; + print ''; + print ''; + print ''; + print $obj->user; + print ''; + print ''; + print $obj->entity; + print ''; + print $numperms; + print ''; + print dol_print_date($db->jdate($obj->date_creation), 'day'); + print ''; + print dol_print_date($db->jdate($obj->date_modification), 'day'); + print ''; + if ($massactionbutton || $massaction) { + $selected = 0; + if (in_array($obj->token_id, $arrayofselected)) { + $selected = 1; + } + print ''; + } + print '
'.$langs->trans("None").'
"; + print '
'; +} + +llxFooter(); +$db->close(); diff --git a/htdocs/core/lib/api.lib.php b/htdocs/core/lib/api.lib.php new file mode 100644 index 00000000000..fcc16f0a29a --- /dev/null +++ b/htdocs/core/lib/api.lib.php @@ -0,0 +1,50 @@ + + * Copyright (C) 2005-2012 Regis Houssin + * Copyright (C) 2024 MDW + * + * 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 . + * or see https://www.gnu.org/ + */ + +/** + * \file htdocs/core/lib/propal.lib.php + * \brief Ensemble de functions de base pour le module propal + * \ingroup propal + */ + +/** + * Return array head with list of tabs to view object information. + * + * @return array head array with tabs + */ +function api_admin_prepare_head() +{ + global $langs, $conf, $user, $db; + + $h = 0; + $head = array(); + + $head[$h][0] = DOL_URL_ROOT.'/api/admin/index.php'; + $head[$h][1] = $langs->trans("Parameter"); + $head[$h][2] = 'parameter'; + $h++; + + $head[$h][0] = DOL_URL_ROOT.'/api/admin/token_list.php'; + $head[$h][1] = $langs->trans("ListOfTokens"); + $head[$h][2] = 'token_list'; + $h++; + + return $head; +} diff --git a/htdocs/langs/en_US/users.lang b/htdocs/langs/en_US/users.lang index 56c4f99a6cb..40986e0c8c5 100644 --- a/htdocs/langs/en_US/users.lang +++ b/htdocs/langs/en_US/users.lang @@ -146,7 +146,9 @@ NewEmailUserClone=Email address of the new user SocialNetworksUser=Social networks for user ApiToken=Api token ListOfTokensForUser=List of tokens for this user +ListOfTokensForAllUsers=List of tokens for all users ListOfRightsForToken=List of rights for this token NumberOfPermissions=Number of permissions DeleteToken=Delete token ConfirmDeleteToken=Are you sure you want to delete this token? +ListOfTokens=List of tokens From 2a452876b3142e30fe9db470cc3360926521c6ff Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 1 Jul 2025 12:11:37 +0200 Subject: [PATCH 031/387] refactor: clean query and add missing filter --- htdocs/user/api_token/list.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index af1dd573719..9af1dabfd96 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -216,7 +216,9 @@ if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { /* The fast and low memory method to get and count full list converts the sql into a sql count */ $sqlforcount = 'SELECT COUNT(*) as nbtotalofrecords'; $sqlforcount .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; - $sqlforcount .= " WHERE entity IN (".$conf->entity.") AND fk_user = ".$id; + $sqlforcount .= " WHERE entity IN (".$conf->entity.")"; + $sqlforcount .= " AND fk_user = ".$id; + $sqlforcount .= " AND service = 'dolibarr_rest_api'"; $resql = $db->query($sqlforcount); if ($resql) { $objforcount = $db->fetch_object($resql); @@ -234,7 +236,9 @@ if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { $sql = "SELECT oat.rowid as token_id, oat.token, oat.entity, oat.state as rights, oat.datec as date_creation, oat.tms as date_modification"; $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; -$sql .= " WHERE oat.fk_user = ".((int) $object->id)." AND entity IN (".$conf->entity.")"; +$sql .= " WHERE oat.fk_user = ".((int) $object->id); +$sql .= " AND entity IN (".$conf->entity.")"; +$sql .= " AND service = 'dolibarr_rest_api'"; if ($search_token) { $sql .= natural_search('oat.token', $search_token); } From 40f838ba58d67b3a97d0a627b5876480e1754f90 Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 1 Jul 2025 12:15:56 +0200 Subject: [PATCH 032/387] refactor: remove useless missing translation --- htdocs/user/api_token/card.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index f94c4f9c2bf..451e2b57009 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -377,7 +377,7 @@ if ($action == 'create') { print dol_get_fiche_end(); print '
'; - print ''; + print ''; print ''; print '
'; From 74acc59da52621c1df5299bdc6d681d258fac246 Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 1 Jul 2025 14:39:27 +0200 Subject: [PATCH 033/387] feat: create token for choosen user as admin --- htdocs/langs/en_US/users.lang | 1 + htdocs/user/api_token/card.php | 72 ++++++++++++++++++++++------------ 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/htdocs/langs/en_US/users.lang b/htdocs/langs/en_US/users.lang index 40986e0c8c5..74bd2b7e848 100644 --- a/htdocs/langs/en_US/users.lang +++ b/htdocs/langs/en_US/users.lang @@ -152,3 +152,4 @@ NumberOfPermissions=Number of permissions DeleteToken=Delete token ConfirmDeleteToken=Are you sure you want to delete this token? ListOfTokens=List of tokens +NewToken=New token diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 451e2b57009..d60e108f4cf 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -54,7 +54,7 @@ $rights = GETPOSTINT('rights'); $cancel = GETPOST('cancel', 'alpha'); $backtopage = GETPOST('backtopage', 'alpha'); -if (!isset($id) || empty($id)) { +if (empty($id) && $action != 'add' && $action != 'create') { accessforbidden(); } @@ -248,6 +248,8 @@ if (empty($reshook)) { if ($action == 'add') { $tokenstring = GETPOST('api_key', 'alphanohtml'); + $userid = GETPOSTINT('user'); + $useridtoadd = !empty($userid) && $userid > 0 ? $userid : $id; if (empty($tokenstring)) { setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("ApiToken")), null, 'errors'); @@ -255,6 +257,12 @@ if (empty($reshook)) { $error++; } + if (empty($useridtoadd)) { + setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("User")), null, 'errors'); + $action = 'create'; + $error++; + } + $sqlforcount = 'SELECT COUNT(*) as nbtotalofrecords'; $sqlforcount .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; $sqlforcount .= " WHERE token = '".$db->escape($tokenstring)."'"; @@ -276,7 +284,7 @@ if (empty($reshook)) { $db->begin(); $sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token (service, token, fk_user, entity, datec)"; - $sql .= " VALUES ('dolibarr_rest_api', '".$db->escape($tokenstring)."', ".($id).", ".((int) $conf->entity).", '".$db->idate(dol_now())."')"; + $sql .= " VALUES ('dolibarr_rest_api', '".$db->escape($tokenstring)."', ".($useridtoadd).", ".((int) $conf->entity).", '".$db->idate(dol_now())."')"; $resql = $db->query($sql); if (!$resql) { @@ -285,7 +293,7 @@ if (empty($reshook)) { } else { $insertedtokenid = $db->last_insert_id(MAIN_DB_PREFIX."oauth_token"); $db->commit(); - header("Location: ".$_SERVER["PHP_SELF"].'?id='.$object->id.'&tokenid='.$insertedtokenid); + header("Location: ".$_SERVER["PHP_SELF"].'?id='.$useridtoadd.'&tokenid='.$insertedtokenid); exit; } } @@ -318,8 +326,12 @@ if (isset($reloadtoken)) { // If we add or del rights, we want to refresh the to * View */ -$person_name = !empty($object->firstname) ? $object->lastname.", ".$object->firstname : $object->lastname; -$title = $person_name." - ".$langs->trans('Card'); +if ($object->id > 0) { + $person_name = !empty($object->firstname) ? $object->lastname.", ".$object->firstname : $object->lastname; + $title = $person_name." - ".$langs->trans('Card'); +} else { + $title = $langs->trans("NewToken"); +} $help_url = ''; llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-user page-card_param_ihm'); @@ -332,27 +344,8 @@ if ($action == 'delete') { print $formconfirm; -$arrayofselected = is_array($toselect) ? $toselect : array(); - -$head = user_prepare_head($object); - -$title = $langs->trans("User"); - -print dol_get_fiche_head($head, 'apitoken', $title, -1, 'user'); - -$linkback = ''.$langs->trans("BackToList").''; - -$morehtmlref = ''; -$morehtmlref .= img_picto($langs->trans("Download").' '.$langs->trans("VCard"), 'vcard.png', 'class="valignmiddle marginleftonly paddingrightonly"'); -$morehtmlref .= ''; - -$urltovirtualcard = '/user/virtualcard.php?id='.((int) $object->id); -$morehtmlref .= dolButtonToOpenUrlInDialogPopup('publicvirtualcard', $langs->transnoentitiesnoconv("PublicVirtualCardUrl").' - '.$object->getFullName($langs), img_picto($langs->trans("PublicVirtualCardUrl"), 'card', 'class="valignmiddle marginleftonly paddingrightonly"'), $urltovirtualcard, '', 'nohover'); - -// Disabled prev/next because there is no token object -dol_banner_tab($object, '', $linkback, $user->hasRight("user", "user", "read") || $user->admin, 'none', '', $morehtmlref); - if ($action == 'create') { + print load_fiche_titre($title, '', 'user'); print '
'; print ''; print ''; @@ -362,7 +355,14 @@ if ($action == 'create') { print ''; - print ''; + if ($user->admin) { + print ''; + print ''; + } else { + print ''; + } print ''; print ''; @@ -384,6 +384,26 @@ if ($action == 'create') { print ""; } elseif ($id > 0 && !empty($token)) { + $arrayofselected = is_array($toselect) ? $toselect : array(); + + $head = user_prepare_head($object); + + $title = $langs->trans("User"); + + print dol_get_fiche_head($head, 'apitoken', $title, -1, 'user'); + + $linkback = ''.$langs->trans("BackToList").''; + + $morehtmlref = ''; + $morehtmlref .= img_picto($langs->trans("Download").' '.$langs->trans("VCard"), 'vcard.png', 'class="valignmiddle marginleftonly paddingrightonly"'); + $morehtmlref .= ''; + + $urltovirtualcard = '/user/virtualcard.php?id='.((int) $object->id); + $morehtmlref .= dolButtonToOpenUrlInDialogPopup('publicvirtualcard', $langs->transnoentitiesnoconv("PublicVirtualCardUrl").' - '.$object->getFullName($langs), img_picto($langs->trans("PublicVirtualCardUrl"), 'card', 'class="valignmiddle marginleftonly paddingrightonly"'), $urltovirtualcard, '', 'nohover'); + + // Disabled prev/next because there is no token object + dol_banner_tab($object, '', $linkback, $user->hasRight("user", "user", "read") || $user->admin, 'none', '', $morehtmlref); + // Tokens info print '
'; print '
'; From d57ca1f2a77c7a11d042c74fbf55b4512d57923e Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 1 Jul 2025 14:46:13 +0200 Subject: [PATCH 034/387] feat: mass delete from admi token list --- htdocs/api/admin/token_list.php | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php index a359ede7c82..06f33ffdf22 100644 --- a/htdocs/api/admin/token_list.php +++ b/htdocs/api/admin/token_list.php @@ -45,6 +45,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/api.lib.php'; // Load translation files required by the page $langs->loadLangs(array('admin', 'users')); +$error = 0; if (!$user->admin) { accessforbidden(); @@ -135,6 +136,46 @@ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x' || GETPOST('button_search_x', 'alpha') || GETPOST('button_search.x', 'alpha') || GETPOST('button_search', 'alpha')) { $massaction = ''; // Protection to avoid mass action if we force a new search during a mass action confirmation } +if (($action == 'delete' && $confirm == 'yes')) { + $db->begin(); + + $nbok = 0; + $TMsg = array(); + + //$toselect could contain duplicate entries, cf https://github.com/Dolibarr/dolibarr/issues/26244 + $unique_arr = array_unique($toselect); + foreach ($unique_arr as $toselectid) { + $sql = "DELETE FROM ".MAIN_DB_PREFIX."oauth_token"; + $sql .= " WHERE rowid = '".$toselectid."'"; + $sql .= " AND service = 'dolibarr_rest_api'"; + + $result = $db->query($sql); + + if ($result > 0) { + $nbok++; + } else { + setEventMessages($db->error(), null, 'errors'); + $error++; + break; + } + } + + if (empty($error)) { + // Message for elements well deleted + if ($nbok > 1) { + setEventMessages($langs->trans("RecordsDeleted", $nbok), null, 'mesgs'); + } elseif ($nbok > 0) { + setEventMessages($langs->trans("RecordDeleted", $nbok), null, 'mesgs'); + } else { + setEventMessages($langs->trans("NoRecordDeleted"), null, 'mesgs'); + } + $db->commit(); + } else { + $db->rollback(); + } + + //var_dump($listofobjectthirdparties);exit; +} /* * View From df3e872d3a1258c2cd162891dd423b07531f3300 Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 1 Jul 2025 14:48:53 +0200 Subject: [PATCH 035/387] feat: disable display when multicompany is not enable --- htdocs/user/api_token/card.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index d60e108f4cf..1c9c316befb 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -363,7 +363,10 @@ if ($action == 'create') { } else { print '
'; } - print ''; + + if (isModEnabled('multicompany')) { + print ''; + } print ''; print ''; } - if (isModEnabled('multicompany')) { - print ''; + if (isModEnabled('multicompany') && is_object($mc)) { + $mc->getInfo($conf->entity); + print ''; } print ''; @@ -441,10 +454,11 @@ if ($action == 'create') { print ''."\n"; // Entity - if (isModEnabled('multicompany')) { + if (isModEnabled('multicompany') && is_object($mc)) { + $mc->getInfo($conf->entity); print ''; print ''; print ''."\n"; } From 0814fb020135c779a0ebd1e4b88782d75391632b Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 1 Jul 2025 16:02:29 +0200 Subject: [PATCH 037/387] feat: multicompany display for admin token list --- htdocs/api/admin/token_list.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php index 06f33ffdf22..ec2daaadc54 100644 --- a/htdocs/api/admin/token_list.php +++ b/htdocs/api/admin/token_list.php @@ -101,7 +101,7 @@ if (!$sortorder) { $arrayfields = array( 'oat.token' => array('label' => "ApiToken", 'checked' => '1'), 'u.login' => array('label' => "User", 'checked' => '1'), - 'oat.entity' => array('label' => "Entity", 'checked' => '1'), + 'e.label' => array('label' => "Entity", 'checked' => '1'), 'oat.datec' => array('label' => "DateCreation", 'checked' => '1'), 'oat.tms' => array('label' => "DateModification", 'checked' => '1'), ); @@ -204,8 +204,14 @@ if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { } $sql = "SELECT oat.rowid as token_id, oat.token, oat.entity, oat.state as rights, oat.fk_user as user_id, u.login as user, oat.datec as date_creation, oat.tms as date_modification"; +if (isModEnabled('multicompany')) { + $sql .= ", e.label as entity_name"; +} $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; $sql .= " JOIN llx_user as u ON u.rowid = oat.fk_user"; +if (isModEnabled('multicompany')) { + $sql .= " JOIN ".$db->prefix()."entity as e ON oat.entity = e.rowid"; +} $sql .= " WHERE oat.entity IN (".$conf->entity.")"; $sql .= " AND service = 'dolibarr_rest_api'"; if ($search_token) { @@ -215,7 +221,7 @@ if ($search_user) { $sql .= natural_search('u.login', $search_user); } if ($search_entity) { - $sql .= natural_search('oat.entity', $search_entity); + $sql .= natural_search('e.label', $search_entity); } if ($search_datec_start) { $sql .= " AND oat.datec >= '".$db->idate($search_datec_start)."'"; @@ -341,7 +347,7 @@ if (empty($reshook)) { } // Entity - if (!empty($arrayfields['oat.entity']['checked']) && isModEnabled('multicompany')) { + if (!empty($arrayfields['e.label']['checked']) && isModEnabled('multicompany')) { print ''; @@ -398,8 +404,8 @@ if (empty($reshook)) { if (!empty($arrayfields['u.login']['checked'])) { print_liste_field_titre($arrayfields['u.login']['label'], $_SERVER["PHP_SELF"], 'u.login', '', $param, '', $sortfield, $sortorder); } - if (!empty($arrayfields['oat.entity']['checked']) && isModEnabled('multicompany')) { - print_liste_field_titre($arrayfields['oat.entity']['label'], $_SERVER["PHP_SELF"], 'oat.entity', '', $param, '', $sortfield, $sortorder); + if (!empty($arrayfields['e.label']['checked']) && isModEnabled('multicompany')) { + print_liste_field_titre($arrayfields['e.label']['label'], $_SERVER["PHP_SELF"], 'e.label', '', $param, '', $sortfield, $sortorder); } print ''; if (!empty($arrayfields['oat.datec']['checked'])) { @@ -451,7 +457,7 @@ if (empty($reshook)) { print ''; if (isModEnabled('multicompany')) { print ''; } print ''; } @@ -466,8 +472,8 @@ if (empty($reshook)) { if (!empty($arrayfields['oat.token']['checked'])) { print_liste_field_titre($arrayfields['oat.token']['label'], $_SERVER["PHP_SELF"], 'oat.token', '', $param, '', $sortfield, $sortorder); } - if (!empty($arrayfields['oat.entity']['checked']) && isModEnabled('multicompany')) { - print_liste_field_titre($arrayfields['oat.entity']['label'], $_SERVER["PHP_SELF"], 'oat.entity', '', $param, '', $sortfield, $sortorder); + if (!empty($arrayfields['e.label']['checked']) && isModEnabled('multicompany')) { + print_liste_field_titre($arrayfields['e.label']['label'], $_SERVER["PHP_SELF"], 'e.label', '', $param, '', $sortfield, $sortorder); } print ''; if (!empty($arrayfields['oat.datec']['checked'])) { @@ -514,7 +520,7 @@ if (empty($reshook)) { print ''; if (isModEnabled('multicompany')) { print ''; } print ''; print ''; print ''; print ''."\n"; diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index c5eef1e14df..6321cf80c23 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -515,7 +515,7 @@ if (empty($reshook)) { } print ''; if (isModEnabled('multicompany')) { From 389715777e561aabba6e3de8b76ca5989f238a84 Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 2 Jul 2025 11:21:38 +0200 Subject: [PATCH 042/387] feat: improve display entity name --- htdocs/api/admin/token_list.php | 3 +++ htdocs/user/api_token/card.php | 3 +++ htdocs/user/api_token/list.php | 3 +++ 3 files changed, 9 insertions(+) diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php index ac842f1af4b..732c7f686c3 100644 --- a/htdocs/api/admin/token_list.php +++ b/htdocs/api/admin/token_list.php @@ -459,7 +459,10 @@ if (empty($reshook)) { print ''; if (isModEnabled('multicompany')) { print ''; } print ''; print ''; print ''."\n"; } diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index 6321cf80c23..0b2df474245 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -520,7 +520,10 @@ if (empty($reshook)) { print ''; if (isModEnabled('multicompany')) { print ''; } print ''; print ''; } else { print ''; From f0be6e458c61c76668ee961d2ffc8321e3ee9154 Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 2 Jul 2025 16:58:44 +0200 Subject: [PATCH 045/387] fix: php phan/stan --- htdocs/user/api_token/card.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index e6756b1074b..26257b1ba80 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -126,6 +126,8 @@ if (empty($reshook)) { $rigthsarray = []; if (!empty($rights)) { + $module = $perms = $subperms = ''; + $sql = "SELECT module, perms, subperms"; $sql .= " FROM ".$db->prefix()."rights_def"; $sql .= " WHERE id = ".((int) $rights); @@ -284,7 +286,7 @@ if (empty($reshook)) { } else { dol_print_error($db); } - if ($nbtotalofrecords > 0) { + if (empty($nbtotalofrecords) || $nbtotalofrecords > 0) { setEventMessages($langs->trans("ErrorFieldExist", $langs->transnoentitiesnoconv("ApiToken")), null, 'errors'); $action = 'create'; $error++; @@ -376,10 +378,10 @@ if ($action == 'create') { print ''; print ''; } else { - print ''; + print ''; } if (isModEnabled('multicompany') && is_object($mc)) { @@ -404,7 +406,6 @@ if ($action == 'create') { print ''; print ""; - } elseif ($id > 0 && !empty($token)) { $arrayofselected = is_array($toselect) ? $toselect : array(); @@ -574,7 +575,7 @@ if ($action == 'create') { $sql = "SELECT r.id, r.libelle as label, r.module, r.perms, r.subperms, r.module_position, r.bydefault"; $sql .= " FROM ".MAIN_DB_PREFIX."rights_def as r"; $sql .= " WHERE r.libelle NOT LIKE 'tou%'"; // On ignore droits "tous" - $sql .= " AND r.entity = ".((int)$entity); + $sql .= " AND r.entity = ".((int) $entity); $sql .= " ORDER BY r.family_position, r.module_position, r.module, r.id"; $result = $db->query($sql); @@ -622,9 +623,9 @@ if ($action == 'create') { $newmoduleposition += 100000; } - $sqlupdate = 'UPDATE '.MAIN_DB_PREFIX."rights_def SET module_position = ".((int)$newmoduleposition).","; - $sqlupdate .= " family_position = ".((int)$familyposition); - $sqlupdate .= " WHERE module_position = ".((int)$obj->module_position)." AND module = '".$db->escape($obj->module)."'"; + $sqlupdate = 'UPDATE '.MAIN_DB_PREFIX."rights_def SET module_position = ".((int) $newmoduleposition).","; + $sqlupdate .= " family_position = ".((int) $familyposition); + $sqlupdate .= " WHERE module_position = ".((int) $obj->module_position)." AND module = '".$db->escape($obj->module)."'"; $db->query($sqlupdate); } @@ -638,13 +639,13 @@ if ($action == 'create') { // Users perms $sql = "SELECT ur.fk_id"; $sql .= " FROM ".MAIN_DB_PREFIX."user_rights as ur"; - $sql .= " WHERE ur.entity = ".((int)$entity); - $sql .= " AND ur.fk_user = ".((int)$object->id); + $sql .= " WHERE ur.entity = ".((int) $entity); + $sql .= " AND ur.fk_user = ".((int) $object->id); $sql .= " UNION "; // Groups perms $sql .= "SELECT gr.fk_id"; $sql .= " FROM ".MAIN_DB_PREFIX."usergroup_rights as gr"; - $sql .= " WHERE EXISTS(SELECT gu.rowid FROM llx_usergroup_user as gu WHERE gu.fk_user = ".((int)$id)." AND gu.fk_usergroup = gr.fk_usergroup)"; + $sql .= " WHERE EXISTS(SELECT gu.rowid FROM llx_usergroup_user as gu WHERE gu.fk_user = ".((int) $id)." AND gu.fk_usergroup = gr.fk_usergroup)"; dol_syslog("get user perms", LOG_DEBUG); $result = $db->query($sql); @@ -667,7 +668,7 @@ if ($action == 'create') { $sql = "SELECT r.id, r.libelle as label, r.module, r.perms, r.subperms, r.module_position, r.bydefault"; $sql .= " FROM ".MAIN_DB_PREFIX."rights_def as r"; $sql .= " WHERE r.libelle NOT LIKE 'tou%'"; // On ignore droits "tous" - $sql .= " AND r.entity = ".((int)$entity); + $sql .= " AND r.entity = ".((int) $entity); $sql .= " AND r.id IN (".implode(', ', $allusersperms).")"; if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled @@ -1035,7 +1036,6 @@ if ($action == 'create') { //$parameters = array('caneditgroup' => $permissiontoeditgroup, 'groupslist' => $groupslist, 'exclude' => $exclude); //$reshook = $hookmanager->executeHooks('formAddUserToGroup', $parameters, $object, $action); // Note that $action and $object may have been modified by hook //print $hookmanager->resPrint; - } if (isModEnabled('api') && $action == 'create') { From 3efe26ac4799f9b25dd83f0e80b8dd8c74054f02 Mon Sep 17 00:00:00 2001 From: yannis Date: Thu, 3 Jul 2025 11:38:33 +0200 Subject: [PATCH 046/387] feat: replaced sql migration file by php upgrade2 --- .../install/mysql/migration/22.0.0-23.0.0.sql | 35 -------- htdocs/install/upgrade2.php | 87 +++++++++++++++++++ htdocs/langs/en_US/install.lang | 2 + 3 files changed, 89 insertions(+), 35 deletions(-) delete mode 100644 htdocs/install/mysql/migration/22.0.0-23.0.0.sql diff --git a/htdocs/install/mysql/migration/22.0.0-23.0.0.sql b/htdocs/install/mysql/migration/22.0.0-23.0.0.sql deleted file mode 100644 index f83e3775ef7..00000000000 --- a/htdocs/install/mysql/migration/22.0.0-23.0.0.sql +++ /dev/null @@ -1,35 +0,0 @@ --- --- This file is executed by calling /install/index.php page --- when current version is higher than the name of this file. --- Be carefull in the position of each SQL request. --- --- To restrict request to Mysql version x.y minimum use -- VMYSQLx.y --- To restrict request to Pgsql version x.y minimum use -- VPGSQLx.y --- To rename a table: ALTER TABLE llx_table RENAME TO llx_table_new; -- Note that "RENAME TO" is both compatible mysql/postgesql, not "RENAME" alone. --- To add a column: ALTER TABLE llx_table ADD COLUMN newcol varchar(60) NOT NULL DEFAULT '0' AFTER existingcol; --- To rename a column: ALTER TABLE llx_table CHANGE COLUMN oldname newname varchar(60); --- To drop a column: ALTER TABLE llx_table DROP COLUMN oldname; --- To change type of field: ALTER TABLE llx_table MODIFY COLUMN name varchar(60); --- To drop a foreign key or constraint: ALTER TABLE llx_table DROP FOREIGN KEY fk_name; --- To create a unique index: ALTER TABLE llx_table ADD UNIQUE INDEX uk_table_field (field); --- To drop an index: -- VMYSQL4.1 DROP INDEX nomindex ON llx_table; --- To drop an index: -- VPGSQL8.2 DROP INDEX nomindex; --- To make pk to be auto increment (mysql): --- -- VMYSQL4.3 ALTER TABLE llx_table ADD PRIMARY KEY(rowid); --- -- VMYSQL4.3 ALTER TABLE llx_table CHANGE COLUMN rowid rowid INTEGER NOT NULL AUTO_INCREMENT; --- To make pk to be auto increment (postgres): --- -- VPGSQL8.2 CREATE SEQUENCE llx_table_rowid_seq OWNED BY llx_table.rowid; --- -- VPGSQL8.2 ALTER TABLE llx_table ADD PRIMARY KEY (rowid); --- -- VPGSQL8.2 ALTER TABLE llx_table ALTER COLUMN rowid SET DEFAULT nextval('llx_table_rowid_seq'); --- -- VPGSQL8.2 SELECT setval('llx_table_rowid_seq', MAX(rowid)) FROM llx_table; --- To set a field as NULL: -- VMYSQL4.3 ALTER TABLE llx_table MODIFY COLUMN name varchar(60) NULL; --- To set a field as NULL: -- VPGSQL8.2 ALTER TABLE llx_table ALTER COLUMN name DROP NOT NULL; --- To set a field as NOT NULL: -- VMYSQL4.3 ALTER TABLE llx_table MODIFY COLUMN name varchar(60) NOT NULL; --- To set a field as NOT NULL: -- VPGSQL8.2 ALTER TABLE llx_table ALTER COLUMN name SET NOT NULL; --- To set a field as default NULL: -- VPGSQL8.2 ALTER TABLE llx_table ALTER COLUMN name SET DEFAULT NULL; --- Note: fields with type BLOB/TEXT can't have default value. --- To rebuild sequence for postgresql after insert, by forcing id autoincrement fields: --- -- VPGSQL8.2 SELECT dol_util_rebuild_sequences(); - - -INSERT INTO llx_oauth_token (service, token, state, fk_user, datec, entity) SELECT 'dolibarr_rest_api' AS service, u.api_key AS token, GROUP_CONCAT(rights.fk_id SEPARATOR ',') AS state, u.rowid AS fk_user, CURRENT_TIMESTAMP AS datec, rights.entity FROM llx_user AS u LEFT JOIN (SELECT fk_user, fk_id, entity FROM llx_user_rights UNION SELECT gu.fk_user, gr.fk_id, gr.entity FROM llx_usergroup_user AS gu JOIN llx_usergroup_rights AS gr on gu.fk_usergroup = gr.fk_usergroup) AS rights on rights.fk_user = u.rowid WHERE u.api_key IS NOT NULL GROUP BY u.login, u.api_key, u.rowid, rights.entity; diff --git a/htdocs/install/upgrade2.php b/htdocs/install/upgrade2.php index 543edde9fd4..53191853413 100644 --- a/htdocs/install/upgrade2.php +++ b/htdocs/install/upgrade2.php @@ -537,6 +537,13 @@ if (!GETPOST('action', 'aZ09') || preg_match('/upgrade/i', GETPOST('action', 'aZ if (versioncompare($versiontoarray, $afterversionarray) >= 0 && versioncompare($versiontoarray, $beforeversionarray) <= 0) { migrate_accountingbookkeeping($entity); } + + // Scripts for 23.0 + $afterversionarray = explode('.', '22.0.9'); + $beforeversionarray = explode('.', '23.0.9'); + if (versioncompare($versiontoarray, $afterversionarray) >= 0 && versioncompare($versiontoarray, $beforeversionarray) <= 0) { + migrate_apiresttokens(); + } } @@ -5282,3 +5289,83 @@ function migrate_accountingbookkeeping(int $entity) print '\n"; } } + +/** + * Migrate API key in oauth_token table + * + * @return void + */ +function migrate_apiresttokens() +{ + global $db, $langs; + + print ''; + print '\n"; + } else { + print $langs->trans('MigratedTokens', $nbofmigration); + print ''; + } +} diff --git a/htdocs/langs/en_US/install.lang b/htdocs/langs/en_US/install.lang index 2b3db368aab..38b02f2affc 100644 --- a/htdocs/langs/en_US/install.lang +++ b/htdocs/langs/en_US/install.lang @@ -221,3 +221,5 @@ MigrationContractLineRank=Migrate Contract Line to use Rank (and enable Reorder) MigrationProductLotPath=Migrate Product Batch files path MigrationAccountancyBookkeeping=Migrate accountancy bookkeeping to use a ref InvoiceExportModelsMigration=Migrate invoice export models +MigrationApiRestTokens=Migrate user's API keys to llx_oauth_token +MigratedTokens=%s token(s) migrated From 8b4c3ef80cf045d655a59bcf2a07228ed42a7ef9 Mon Sep 17 00:00:00 2001 From: yannis Date: Thu, 3 Jul 2025 12:08:46 +0200 Subject: [PATCH 047/387] fix: bad test that unable to create token --- htdocs/user/api_token/card.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 26257b1ba80..c0682d35da9 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -286,7 +286,7 @@ if (empty($reshook)) { } else { dol_print_error($db); } - if (empty($nbtotalofrecords) || $nbtotalofrecords > 0) { + if (!empty($nbtotalofrecords) && $nbtotalofrecords > 0) { setEventMessages($langs->trans("ErrorFieldExist", $langs->transnoentitiesnoconv("ApiToken")), null, 'errors'); $action = 'create'; $error++; From 25ca6582b2e971284ecc9fe2b6bda94ed4ff2971 Mon Sep 17 00:00:00 2001 From: yannis Date: Thu, 3 Jul 2025 12:10:19 +0200 Subject: [PATCH 048/387] feat: ungrant perm to tokens if lost in global --- htdocs/user/class/user.class.php | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 33599cebf80..4c3b33f56b8 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -1269,6 +1269,38 @@ class User extends CommonObject $error++; dol_print_error($this->db); } + + if (!$error) { + $idtodelete = array(); + + $sqlusertokens = "SELECT oat.rowid, oat.state as rights"; + $sqlusertokens .= " FROM llx_oauth_token AS oat"; + $sqlusertokens .= " WHERE oat.fk_user = ".((int) $this->id); + + $idtodeletequery = $this->db->query($sql); + $resulttokens = $this->db->query($sqlusertokens); + if ($resulttokens && $idtodeletequery) { + while ($idobj = $this->db->fetch_object($idtodeletequery)) { + $idtodelete []= $idobj->id; + } + + while ($obj = $this->db->fetch_object($resulttokens)) { + if (!empty($obj->rights)) { + $newtokenrigths = array_diff(explode(',', $obj->rights), $idtodelete); + + $sqlupdate = "UPDATE ".MAIN_DB_PREFIX."oauth_token"; + $sqlupdate.= " SET state = '".$this->db->escape(preg_replace('/\s+/', '', implode(',', $newtokenrigths)))."'"; + $sqlupdate.= " WHERE rowid = '".$obj->rowid."'"; + + $resupdate = $this->db->query($sqlupdate); + if (!$resupdate) { + $error++; + dol_print_error($this->db); + } + } + } + } + } } if (!$error && !$notrigger) { From 0d75748e0178114600e1bc37e55373927e58b1e6 Mon Sep 17 00:00:00 2001 From: yannis Date: Thu, 3 Jul 2025 14:40:44 +0200 Subject: [PATCH 049/387] feat: ungrant perm to tokens if lost in group --- htdocs/user/class/usergroup.class.php | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/htdocs/user/class/usergroup.class.php b/htdocs/user/class/usergroup.class.php index 04f014d7133..86d17de4fda 100644 --- a/htdocs/user/class/usergroup.class.php +++ b/htdocs/user/class/usergroup.class.php @@ -531,6 +531,7 @@ class UserGroup extends CommonObject $sql .= " AND ".$wherefordel; } + $sqlforid = $sql; $result = $this->db->query($sql); if ($result) { $num = $this->db->num_rows($result); @@ -557,6 +558,40 @@ class UserGroup extends CommonObject dol_print_error($this->db); } + if (!$error) { + $idtodelete = array(); + + $sqlusertokens = "SELECT oat.rowid, oat.state as rights"; + $sqlusertokens .= " FROM llx_usergroup_user AS gu"; + $sqlusertokens .= " JOIN llx_oauth_token AS oat ON gu.fk_user = oat.fk_user"; + $sqlusertokens .= " WHERE gu.fk_usergroup = ".((int) $this->id); + $sqlusertokens .= " AND oat.service = 'dolibarr_rest_api'"; + + $idtodeletequery = $this->db->query($sqlforid); + $resulttokens = $this->db->query($sqlusertokens); + if ($resulttokens && $idtodeletequery) { + while ($idobj = $this->db->fetch_object($idtodeletequery)) { + $idtodelete []= $idobj->id; + } + + while ($obj = $this->db->fetch_object($resulttokens)) { + if (!empty($obj->rights)) { + $newtokenrigths = array_diff(explode(',', $obj->rights), $idtodelete); + + $sqlupdate = "UPDATE ".MAIN_DB_PREFIX."oauth_token"; + $sqlupdate.= " SET state = '".$this->db->escape(preg_replace('/\s+/', '', implode(',', $newtokenrigths)))."'"; + $sqlupdate.= " WHERE rowid = '".$obj->rowid."'"; + + $resupdate = $this->db->query($sqlupdate); + if (!$resupdate) { + $error++; + dol_print_error($this->db); + } + } + } + } + } + if (!$error) { $langs->load("other"); $this->context = array('audit' => $langs->trans("PermissionsDelete").($rid ? ' (id='.$rid.')' : '')); From 04609c0728bc7c06cb76a3e3c9337319de1e4b26 Mon Sep 17 00:00:00 2001 From: yannis Date: Thu, 3 Jul 2025 14:42:43 +0200 Subject: [PATCH 050/387] feat: avoid modif on token that is not for api service --- htdocs/user/class/user.class.php | 1 + 1 file changed, 1 insertion(+) diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 4c3b33f56b8..307871f4165 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -1276,6 +1276,7 @@ class User extends CommonObject $sqlusertokens = "SELECT oat.rowid, oat.state as rights"; $sqlusertokens .= " FROM llx_oauth_token AS oat"; $sqlusertokens .= " WHERE oat.fk_user = ".((int) $this->id); + $sqlusertokens .= " AND oat.service = 'dolibarr_rest_api'"; $idtodeletequery = $this->db->query($sql); $resulttokens = $this->db->query($sqlusertokens); From b503f10c11c575e1ee812755a787218926b0bff7 Mon Sep 17 00:00:00 2001 From: yannis Date: Thu, 3 Jul 2025 15:43:59 +0200 Subject: [PATCH 051/387] feat: not erasing token perms if user stil has them (himself<->group) --- htdocs/user/class/user.class.php | 11 ++++++++++- htdocs/user/class/usergroup.class.php | 10 +++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 307871f4165..5fb2bfb8d38 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -1273,12 +1273,21 @@ class User extends CommonObject if (!$error) { $idtodelete = array(); + // Query to avoid erasing perms from token if the user still have them inherited by a group + $sqlforid = "SELECT id"; + $sqlforid .= " FROM ".$this->db->prefix()."rights_def"; + $sqlforid .= " WHERE (entity IN (".$this->db->sanitize($entity, 0, 0, 0, 0).")"; + if (!empty($wherefordel) && $wherefordel != 'allmodules') { + $sqlforid .= " AND (".$wherefordel.")"; + } + $sqlforid .= ") AND NOT EXISTS(SELECT gr.fk_id FROM llx_usergroup_rights as gr, llx_usergroup_user as gu WHERE gr.entity = 1 AND gu.entity IN (0,1) AND gr.fk_usergroup = gu.fk_usergroup AND gu.fk_user = 3 AND id = gr.fk_id)"; + $sqlusertokens = "SELECT oat.rowid, oat.state as rights"; $sqlusertokens .= " FROM llx_oauth_token AS oat"; $sqlusertokens .= " WHERE oat.fk_user = ".((int) $this->id); $sqlusertokens .= " AND oat.service = 'dolibarr_rest_api'"; - $idtodeletequery = $this->db->query($sql); + $idtodeletequery = $this->db->query($sqlforid); $resulttokens = $this->db->query($sqlusertokens); if ($resulttokens && $idtodeletequery) { while ($idobj = $this->db->fetch_object($idtodeletequery)) { diff --git a/htdocs/user/class/usergroup.class.php b/htdocs/user/class/usergroup.class.php index 86d17de4fda..698ee6c7758 100644 --- a/htdocs/user/class/usergroup.class.php +++ b/htdocs/user/class/usergroup.class.php @@ -531,7 +531,6 @@ class UserGroup extends CommonObject $sql .= " AND ".$wherefordel; } - $sqlforid = $sql; $result = $this->db->query($sql); if ($result) { $num = $this->db->num_rows($result); @@ -561,6 +560,15 @@ class UserGroup extends CommonObject if (!$error) { $idtodelete = array(); + // Query to avoid erasing perms from token if the user still have them himself + $sqlforid = "SELECT id"; + $sqlforid .= " FROM ".$this->db->prefix()."rights_def"; + $sqlforid .= " WHERE (entity = ".((int) $entity); + if (!empty($wherefordel) && $wherefordel != 'allmodules') { + $sqlforid .= " AND ".$wherefordel; + } + $sqlforid .= ") AND NOT EXISTS(SELECT ur.fk_id FROM llx_user_rights as ur WHERE ur.entity = 1 AND ur.fk_user = 3 AND id = ur.fk_id)"; + $sqlusertokens = "SELECT oat.rowid, oat.state as rights"; $sqlusertokens .= " FROM llx_usergroup_user AS gu"; $sqlusertokens .= " JOIN llx_oauth_token AS oat ON gu.fk_user = oat.fk_user"; From 1ffdd5736a4d555182323ad19c3e2b9fb87f575f Mon Sep 17 00:00:00 2001 From: yannis Date: Thu, 3 Jul 2025 16:03:48 +0200 Subject: [PATCH 052/387] fix: error if user had no perms (himself/group) --- htdocs/user/api_token/card.php | 460 +++++++++++++++++---------------- 1 file changed, 231 insertions(+), 229 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index c0682d35da9..489f5a6a2b4 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -663,77 +663,52 @@ if ($action == 'create') { } // Load and show all the perms grouped by module + if (count($allusersperms) > 0) { + $sql = "SELECT r.id, r.libelle as label, r.module, r.perms, r.subperms, r.module_position, r.bydefault"; + $sql .= " FROM ".MAIN_DB_PREFIX."rights_def as r"; + $sql .= " WHERE r.libelle NOT LIKE 'tou%'"; // On ignore droits "tous" + $sql .= " AND r.entity = ".((int)$entity); + $sql .= " AND r.id IN (".implode(', ', $allusersperms).")"; + if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { + $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled + } + $sql .= " ORDER BY r.family_position, r.module_position, r.module, r.id"; - //print "xx".$conf->global->MAIN_USE_ADVANCED_PERMS; - $sql = "SELECT r.id, r.libelle as label, r.module, r.perms, r.subperms, r.module_position, r.bydefault"; - $sql .= " FROM ".MAIN_DB_PREFIX."rights_def as r"; - $sql .= " WHERE r.libelle NOT LIKE 'tou%'"; // On ignore droits "tous" - $sql .= " AND r.entity = ".((int) $entity); - $sql .= " AND r.id IN (".implode(', ', $allusersperms).")"; - if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { - $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled - } - $sql .= " ORDER BY r.family_position, r.module_position, r.module, r.id"; + $result = $db->query($sql); + if ($result) { + $num = $db->num_rows($result); + $i = 0; + $j = 0; + $oldmod = ''; - $result = $db->query($sql); - if ($result) { - $num = $db->num_rows($result); - $i = 0; - $j = 0; - $oldmod = ''; + $cookietohidegroup = (empty($_COOKIE["DOLUSER_PERMS_HIDE_GRP"]) ? '' : preg_replace('/^,/', '', $_COOKIE["DOLUSER_PERMS_HIDE_GRP"])); + $cookietohidegrouparray = explode(',', $cookietohidegroup); + //var_dump($cookietohidegrouparray); - $cookietohidegroup = (empty($_COOKIE["DOLUSER_PERMS_HIDE_GRP"]) ? '' : preg_replace('/^,/', '', $_COOKIE["DOLUSER_PERMS_HIDE_GRP"])); - $cookietohidegrouparray = explode(',', $cookietohidegroup); - //var_dump($cookietohidegrouparray); + while ($i < $num) { + $obj = $db->fetch_object($result); - while ($i < $num) { - $obj = $db->fetch_object($result); - - // If line is for a module that does not exist anymore (absent of includes/module), we ignore it - if (empty($modules[$obj->module])) { - $i++; - continue; - } - - // Special cases - if (isModEnabled("reception")) { - // The 2 permission in fournisseur modules has been replaced by permissions into reception module - if ($obj->module == 'fournisseur' && $obj->perms == 'commande' && $obj->subperms == 'receptionner') { + // If line is for a module that does not exist anymore (absent of includes/module), we ignore it + if (empty($modules[$obj->module])) { $i++; continue; } - if ($obj->module == 'fournisseur' && $obj->perms == 'commande_advance' && $obj->subperms == 'check') { - $i++; - continue; + + // Special cases + if (isModEnabled("reception")) { + // The 2 permission in fournisseur modules has been replaced by permissions into reception module + if ($obj->module == 'fournisseur' && $obj->perms == 'commande' && $obj->subperms == 'receptionner') { + $i++; + continue; + } + if ($obj->module == 'fournisseur' && $obj->perms == 'commande_advance' && $obj->subperms == 'check') { + $i++; + continue; + } } - } - $objMod = $modules[$obj->module]; + $objMod = $modules[$obj->module]; - if (GETPOSTISSET('forbreakperms_'.$obj->module)) { - $ishidden = GETPOSTINT('forbreakperms_'.$obj->module); - } elseif (in_array($j, $cookietohidegrouparray)) { // If j is among list of hidden group - $ishidden = 1; - } else { - $ishidden = 0; - } - $isexpanded = !$ishidden; - //var_dump("isexpanded=".$isexpanded); - - $permsgroupbyentitypluszero = array(); - if (!empty($permsgroupbyentity[0])) { - $permsgroupbyentitypluszero = array_merge($permsgroupbyentitypluszero, $permsgroupbyentity[0]); - } - if (!empty($permsgroupbyentity[$entity])) { - $permsgroupbyentitypluszero = array_merge($permsgroupbyentitypluszero, $permsgroupbyentity[$entity]); - } - //var_dump($permsgroupbyentitypluszero); - - // Break found, it's a new module to catch - if (isset($obj->module) && ($oldmod != $obj->module)) { - $oldmod = $obj->module; - - $j++; if (GETPOSTISSET('forbreakperms_'.$obj->module)) { $ishidden = GETPOSTINT('forbreakperms_'.$obj->module); } elseif (in_array($j, $cookietohidegrouparray)) { // If j is among list of hidden group @@ -742,115 +717,157 @@ if ($action == 'create') { $ishidden = 0; } $isexpanded = !$ishidden; - //var_dump('$obj->module='.$obj->module.' isexpanded='.$isexpanded); + //var_dump("isexpanded=".$isexpanded); - // Break detected, we get objMod - $objMod = $modules[$obj->module]; - $picto = ($objMod->picto ? $objMod->picto : 'generic'); + $permsgroupbyentitypluszero = array(); + if (!empty($permsgroupbyentity[0])) { + $permsgroupbyentitypluszero = array_merge($permsgroupbyentitypluszero, $permsgroupbyentity[0]); + } + if (!empty($permsgroupbyentity[$entity])) { + $permsgroupbyentitypluszero = array_merge($permsgroupbyentitypluszero, $permsgroupbyentity[$entity]); + } + //var_dump($permsgroupbyentitypluszero); + + // Break found, it's a new module to catch + if (isset($obj->module) && ($oldmod != $obj->module)) { + $oldmod = $obj->module; + + $j++; + if (GETPOSTISSET('forbreakperms_'.$obj->module)) { + $ishidden = GETPOSTINT('forbreakperms_'.$obj->module); + } elseif (in_array($j, $cookietohidegrouparray)) { // If j is among list of hidden group + $ishidden = 1; + } else { + $ishidden = 0; + } + $isexpanded = !$ishidden; + //var_dump('$obj->module='.$obj->module.' isexpanded='.$isexpanded); + + // Break detected, we get objMod + $objMod = $modules[$obj->module]; + $picto = ($objMod->picto ? $objMod->picto : 'generic'); + + // Show break line + print ''; + // Picto and label of module + print ''; + + // Permission and tick (2 columns) + if (($caneditperms && empty($objMod->rights_admin_allowed)) || empty($object->admin)) { + if ($caneditperms) { + print ''; + print ''; + } else { + print ''; + print ''; + } + } else { + if ($caneditperms) { + print ''; + print ''; + } else { + print ''; + print ''; + } + } + + // Description of permission (2 columns) + print ''; + print ''; //Add picto + / - when open en closed + print ''."\n"; + } + + $permlabel = (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && ($langs->trans("PermissionAdvanced".$obj->id) != "PermissionAdvanced".$obj->id) ? $langs->trans("PermissionAdvanced".$obj->id) : (($langs->trans("Permission".$obj->id) != "Permission".$obj->id) ? $langs->trans("Permission".$obj->id) : $langs->trans($obj->label))); + + print ''."\n"; + print ''; - // Show break line - print ''; // Picto and label of module - print ''; // Permission and tick (2 columns) - if (($caneditperms && empty($objMod->rights_admin_allowed)) || empty($object->admin)) { + if (!empty($object->admin) && !empty($objMod->rights_admin_allowed)) { // Permission granted because admin + print ''; if ($caneditperms) { - print ''; - print ''; } else { - print ''; - print ''; - } - } else { - if ($caneditperms) { - print ''; - print ''; + } elseif (in_array($obj->id, $tokenperms)) { // Permission granted by user + print ''; + if ($caneditperms) { + print ''; } else { - print ''; - print ''; + print ''; } - } - - // Description of permission (2 columns) - print ''; - print ''; //Add picto + / - when open en closed - print ''."\n"; - } - - $permlabel = (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && ($langs->trans("PermissionAdvanced".$obj->id) != "PermissionAdvanced".$obj->id) ? $langs->trans("PermissionAdvanced".$obj->id) : (($langs->trans("Permission".$obj->id) != "Permission".$obj->id) ? $langs->trans("Permission".$obj->id) : $langs->trans($obj->label))); - - print ''."\n"; - print ''; - - // Picto and label of module - print ''; - - // Permission and tick (2 columns) - if (!empty($object->admin) && !empty($objMod->rights_admin_allowed)) { // Permission granted because admin - print ''; - if ($caneditperms) { - print ''; - } else { - print ''; - } - print ''; - } elseif (in_array($obj->id, $tokenperms)) { // Permission granted by user - print ''; - if ($caneditperms) { - print ''; - } else { - print ''; - } - print ''; - } elseif (isset($permsgroupbyentitypluszero) && is_array($permsgroupbyentitypluszero)) { - print ''; - if (in_array($obj->id, $permsgroupbyentitypluszero)) { // Permission granted by group - print ''; - print ''; + } elseif (isset($permsgroupbyentitypluszero) && is_array($permsgroupbyentitypluszero)) { + print ''; + if (in_array($obj->id, $permsgroupbyentitypluszero)) { // Permission granted by group + print ''; + print ''; + } else { + // Do not own permission + if ($caneditperms) { + print ''; + } else { + print ''; + } + print ''; + } } else { // Do not own permission + print ''; if ($caneditperms) { print ''; } - } else { - // Do not own permission - print ''; - if ($caneditperms) { - print ''; + + // Description of permission (1 or 2 columns) + if (!$user->admin) { + print ''; + + // Permission id + if ($user->admin) { + print ''; } - print ''; - } - // Description of permission (1 or 2 columns) - if (!$user->admin) { - print ''."\n"; - print $permlabel; - $idtouse = $obj->id; - if (in_array($idtouse, array(121, 122, 125, 126))) { // Force message for the 3 permission on third parties - $idtouse = 122; + $i++; } - if ($langs->trans("Permission".$idtouse.'b') != "Permission".$idtouse.'b') { - print '
'.$langs->trans("Permission".$idtouse.'b').''; - } - if ($langs->trans("Permission".$obj->id.'c') != "Permission".$obj->id.'c') { - print '
'.$langs->trans("Permission".$obj->id.'c').''; - } - if (getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { - if (preg_match('/_advance$/', $obj->perms)) { - print ' ('.$langs->trans("AdvancedModeOnly").')'; - } - } - // Special warning case for the permission "Allow to modify other users password" - if ($obj->module == 'user' && $obj->perms == 'user' && $obj->subperms == 'password') { - if ((!empty($object->admin) && !empty($objMod->rights_admin_allowed)) || - in_array($obj->id, $tokenperms) /* if edited user owns this permissions */ || - (isset($permsgroupbyentitypluszero) && is_array($permsgroupbyentitypluszero) && in_array($obj->id, $permsgroupbyentitypluszero))) { - print ' '.img_warning($langs->trans("AllowPasswordResetBySendingANewPassByEmail")); - } - } - // Special warning case for the permission "Create/modify other users, groups and permissions" - if ($obj->module == 'user' && $obj->perms == 'user' && ($obj->subperms == 'creer' || $obj->subperms == 'create')) { - if ((!empty($object->admin) && !empty($objMod->rights_admin_allowed)) || - in_array($obj->id, $tokenperms) /* if edited user owns this permissions */ || - (isset($permsgroupbyentitypluszero) && is_array($permsgroupbyentitypluszero) && in_array($obj->id, $permsgroupbyentitypluszero))) { - print ' '.img_warning($langs->trans("AllowAnyPrivileges")); - } - } - // Special case for reading bank account when you have permission to manage Chart of account - if ($obj->module == 'banque' && $obj->perms == 'lire') { - if (isModEnabled("accounting") && $object->hasRight('accounting', 'chartofaccount')) { - print ' '.img_warning($langs->trans("WarningReadBankAlsoAllowedIfUserHasPermission")); - } - } - - print ''; - - // Permission id - if ($user->admin) { - print ''; - } - - print ''."\n"; - - $i++; + } else { + dol_print_error($db); } } else { - dol_print_error($db); + print ''; } print '
'.$langs->trans('User').''.$person_name.'
'.$langs->trans('User').''; + print $form->select_dolusers('', 'user', 1, '', 0, '', '', (string) $object->entity, 0, 0, '', 0, '', 'minwidth200 maxwidth500'); + print '
'.$langs->trans('User').''.$person_name.'
'.$langs->trans('Entity').''.$conf->entity.'
'.$langs->trans("ApiToken").'
'.$langs->trans('User').''.$person_name.'
'.$langs->trans('Entity').''.$conf->entity.'
'.$langs->trans('Entity').''.$conf->entity.'
'.$langs->trans("ApiToken").''; From 61601c43d68dd48957e10e01863b063acccde0be Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 1 Jul 2025 15:56:10 +0200 Subject: [PATCH 036/387] feat: multicompany display for token card --- htdocs/user/api_token/card.php | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 1c9c316befb..69cd6e8932a 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -80,7 +80,13 @@ if ($user->id != $id && !$canreaduser) { } $sql = "SELECT oat.rowid as token_id, oat.token, oat.entity, oat.state as rights, oat.datec as date_creation, oat.tms as date_modification"; +if (isModEnabled('multicompany')) { + $sql .= ", e.label"; +} $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; +if (isModEnabled('multicompany')) { + $sql .= " JOIN ".$db->prefix()."entity as e ON oat.entity = e.rowid"; +} $sql .= " WHERE oat.rowid = ".((int) $tokenid); $resql = $db->query($sql); @@ -315,7 +321,13 @@ if (empty($reshook)) { if (isset($reloadtoken)) { // If we add or del rights, we want to refresh the token with its new updated fields $sql = "SELECT oat.rowid as token_id, oat.token, oat.entity, oat.state as rights, oat.datec as date_creation, oat.tms as date_modification"; + if (isModEnabled('multicompany')) { + $sql .= ", e.label"; + } $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; + if (isModEnabled('multicompany')) { + $sql .= " JOIN ".$db->prefix()."entity as e ON oat.entity = e.rowid"; + } $sql .= " WHERE oat.rowid = ".((int) $tokenid); $resql = $db->query($sql); @@ -364,8 +376,9 @@ if ($action == 'create') { print '
'.$langs->trans('User').''.$person_name.'
'.$langs->trans('Entity').''.$conf->entity.'
'.$langs->trans('Entity').''.$mc->label.'
'.$langs->trans("ApiToken").'
'.$langs->trans("Entity").''; - print $token->entity; + print $mc->label; print '
'; print ''; print ''.$langs->trans("NumberOfPermissions").''; - print $obj->entity; + print $obj->entity_name; print ''; From ec79241dc2caae23ba82cf788727e1a92d9431fc Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 1 Jul 2025 16:09:12 +0200 Subject: [PATCH 038/387] feat: multicompany display for token list --- htdocs/user/api_token/list.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index 9af1dabfd96..c5eef1e14df 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -117,7 +117,7 @@ if ($user->id != $id && !$canreaduser) { $arrayfields = array( 'oat.token' => array('label' => "ApiToken", 'checked' => '1'), - 'oat.entity' => array('label' => "Entity", 'checked' => '1'), + 'e.label' => array('label' => "Entity", 'checked' => '1'), 'oat.datec' => array('label' => "DateCreation", 'checked' => '1'), 'oat.tms' => array('label' => "DateModification", 'checked' => '1'), ); @@ -235,7 +235,13 @@ if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { } $sql = "SELECT oat.rowid as token_id, oat.token, oat.entity, oat.state as rights, oat.datec as date_creation, oat.tms as date_modification"; +if (isModEnabled('multicompany')) { + $sql .= ", e.label as entity_name"; +} $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; +if (isModEnabled('multicompany')) { + $sql .= " JOIN ".$db->prefix()."entity as e ON oat.entity = e.rowid"; +} $sql .= " WHERE oat.fk_user = ".((int) $object->id); $sql .= " AND entity IN (".$conf->entity.")"; $sql .= " AND service = 'dolibarr_rest_api'"; @@ -412,9 +418,9 @@ if (empty($reshook)) { } // Entity - if (!empty($arrayfields['oat.entity']['checked']) && isModEnabled('multicompany')) { + if (!empty($arrayfields['e.label']['checked']) && isModEnabled('multicompany')) { print ''; - print ' 0 ? " disabled" : "").'>'; + print ''; print ''.$langs->trans("NumberOfPermissions").''; - print $obj->entity; + print $obj->entity_name; print ''; From f7f7532665d837cf84e53fa54606731d5c45cb89 Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 1 Jul 2025 16:18:00 +0200 Subject: [PATCH 039/387] feat: show tokens from all entities if in master entity in admin --- htdocs/api/admin/token_list.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php index ec2daaadc54..bdcb222a33e 100644 --- a/htdocs/api/admin/token_list.php +++ b/htdocs/api/admin/token_list.php @@ -212,8 +212,10 @@ $sql .= " JOIN llx_user as u ON u.rowid = oat.fk_user"; if (isModEnabled('multicompany')) { $sql .= " JOIN ".$db->prefix()."entity as e ON oat.entity = e.rowid"; } -$sql .= " WHERE oat.entity IN (".$conf->entity.")"; -$sql .= " AND service = 'dolibarr_rest_api'"; +$sql .= " WHERE service = 'dolibarr_rest_api'"; +if (!isModEnabled('multicompany') || $conf->entity > 1) { + $sql .= " AND oat.entity IN (".$conf->entity.")"; +} if ($search_token) { $sql .= natural_search('oat.token', $search_token); } From ef963140269ea5900c4cfa349b0ec8544cffd7ce Mon Sep 17 00:00:00 2001 From: yannis Date: Tue, 1 Jul 2025 16:52:26 +0200 Subject: [PATCH 040/387] fix: cancel button not working when comming from admin token list --- htdocs/api/admin/token_list.php | 2 +- htdocs/user/api_token/card.php | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php index bdcb222a33e..35965a25dc7 100644 --- a/htdocs/api/admin/token_list.php +++ b/htdocs/api/admin/token_list.php @@ -305,7 +305,7 @@ if (GETPOSTINT('nomassaction') || in_array($massaction, array('presend', 'predel $massactionbutton = $form->selectMassAction('', $arrayofmassactions); $morehtmlright = ''; -$tmpurlforbutton = DOL_URL_ROOT.'/user/api_token/card.php?action=create'; +$tmpurlforbutton = DOL_URL_ROOT.'/user/api_token/card.php?action=create&backtopage='.urlencode(DOL_URL_ROOT.'/api/admin/token_list.php'); $morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton); print '
id; + } + if ($cancel) { if (!empty($backtopage)) { header("Location: ".$backtopage); @@ -361,7 +365,7 @@ if ($action == 'create') { print ''; print ''; print ''; - print ''; + print ''; print dol_get_fiche_head(); From e97a1f8efd447a64a10197a13a5db70ae9df3ea7 Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 2 Jul 2025 09:20:54 +0200 Subject: [PATCH 041/387] feat: encrypt/decrypt token --- htdocs/api/admin/token_list.php | 2 +- htdocs/user/api_token/card.php | 9 ++++++--- htdocs/user/api_token/list.php | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php index 35965a25dc7..ac842f1af4b 100644 --- a/htdocs/api/admin/token_list.php +++ b/htdocs/api/admin/token_list.php @@ -449,7 +449,7 @@ if (empty($reshook)) { } print '
'; print ''; - print $obj->token; + print dolDecrypt($obj->token); print ''; print ''; diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 1243aefb0a3..5e7655bfda9 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -275,7 +275,7 @@ if (empty($reshook)) { $sqlforcount = 'SELECT COUNT(*) as nbtotalofrecords'; $sqlforcount .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; - $sqlforcount .= " WHERE token = '".$db->escape($tokenstring)."'"; + $sqlforcount .= " WHERE token = '".$db->escape(dolEncrypt($tokenstring, '', '', 'dolibarr'))."'"; $sqlforcount .= " AND service = 'dolibarr_rest_api'"; $resql = $db->query($sqlforcount); if ($resql) { @@ -294,7 +294,7 @@ if (empty($reshook)) { $db->begin(); $sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token (service, token, fk_user, entity, datec)"; - $sql .= " VALUES ('dolibarr_rest_api', '".$db->escape($tokenstring)."', ".($useridtoadd).", ".((int) $conf->entity).", '".$db->idate(dol_now())."')"; + $sql .= " VALUES ('dolibarr_rest_api', '".$db->escape(dolEncrypt($tokenstring, '', '', 'dolibarr'))."', ".($useridtoadd).", ".((int) $conf->entity).", '".$db->idate(dol_now())."')"; $resql = $db->query($sql); if (!$resql) { @@ -336,6 +336,7 @@ if (isset($reloadtoken)) { // If we add or del rights, we want to refresh the to $resql = $db->query($sql); $token = $db->fetch_object($resql); + $tokenvalue = dolDecrypt($token->token); } /* @@ -412,6 +413,8 @@ if ($action == 'create') { print dol_get_fiche_head($head, 'apitoken', $title, -1, 'user'); + $tokenvalue = dolDecrypt($token->token); + $linkback = ''.$langs->trans("BackToList").''; $morehtmlref = ''; @@ -453,7 +456,7 @@ if ($action == 'create') { // Token print '
'.$langs->trans("ApiToken").''; - print showValueWithClipboardCPButton($token->token, 1, $token->token); + print showValueWithClipboardCPButton($tokenvalue, 1, $tokenvalue); print '
'; print ''; - print $obj->token; + print dolDecrypt($obj->token); print ''; print ''; + print ''; + print ''; print $obj->entity_name; + print ' '; print ''; diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 5e7655bfda9..0431f76c332 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -465,7 +465,10 @@ if ($action == 'create') { $mc->getInfo($conf->entity); print '
'.$langs->trans("Entity").''; + print ''; + print ''; print $mc->label; + print ' '; print '
'; + print ''; + print ''; print $obj->entity_name; + print ' '; print ''; From 4c51a4309eeb93a72c7dd1afd48f01dd0dac9c41 Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 2 Jul 2025 15:22:13 +0200 Subject: [PATCH 043/387] feat: api loading rights of specified token --- htdocs/api/class/api_access.class.php | 12 +++++++----- htdocs/user/class/user.class.php | 4 +++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/htdocs/api/class/api_access.class.php b/htdocs/api/class/api_access.class.php index 6a81b2e5975..831d2785a21 100644 --- a/htdocs/api/class/api_access.class.php +++ b/htdocs/api/class/api_access.class.php @@ -133,10 +133,12 @@ class DolibarrApiAccess implements iAuthenticate if ($api_key) { $userentity = 0; - $sql = "SELECT u.login, u.datec, u.api_key,"; - $sql .= " u.tms as date_modification, u.entity"; - $sql .= " FROM ".MAIN_DB_PREFIX."user as u"; - $sql .= " WHERE u.api_key = '".$this->db->escape($api_key)."' OR u.api_key = '".$this->db->escape(dolEncrypt($api_key, '', '', 'dolibarr'))."'"; + $sql = "SELECT u.login, oat.token as api_key, oat.entity"; + $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token AS oat"; + $sql .= " JOIN ".MAIN_DB_PREFIX."user AS u ON u.rowid = oat.fk_user"; + $sql .= " WHERE oat.token = '".$this->db->escape($api_key)."'"; + $sql .= " OR oat.token = '".$this->db->escape(dolEncrypt($api_key, '', '', 'dolibarr'))."'"; + $sql .= " AND oat.service = 'dolibarr_rest_api'"; $result = $this->db->query($sql); if ($result) { @@ -206,7 +208,7 @@ class DolibarrApiAccess implements iAuthenticate } // User seems valid - $fuser->loadRights(); + $fuser->loadRights('', 0, $stored_key); // Set the property $user to the $user of API static::$user = $fuser; diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 66d7fd152b2..33599cebf80 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -1350,7 +1350,9 @@ class User extends CommonObject if (!empty($token)) { // If token specified, we only load perms from it $sql = "SELECT oat.state as rights, oat.entity"; $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; - $sql .= " WHERE oat.token = '".($this->db->escape($token))."'"; + $sql .= " WHERE oat.token = '".$this->db->escape($token)."'"; + $sql .= " OR oat.token = '".$this->db->escape(dolEncrypt($token, '', '', 'dolibarr'))."'"; + $sql .= " AND oat.service = 'dolibarr_rest_api'"; $resql = $this->db->query($sql); if ($resql) { From 00187b3d7cdc599d1352e220d64fb8e4c546b1e7 Mon Sep 17 00:00:00 2001 From: yannis Date: Wed, 2 Jul 2025 15:51:24 +0200 Subject: [PATCH 044/387] feat: auto selected user at token creation if id set --- htdocs/user/api_token/card.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 0431f76c332..e6756b1074b 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -375,7 +375,8 @@ if ($action == 'create') { if ($user->admin) { print '
'.$langs->trans('User').''; - print $form->select_dolusers('', 'user', 1, '', 0, '', '', (string) $object->entity, 0, 0, '', 0, '', 'minwidth200 maxwidth500'); + $selecteduser = !empty($id) ? $id : ''; + print $form->select_dolusers($selecteduser, 'user', 1, '', 0, '', '', (string) $object->entity, 0, 0, '', 0, '', 'minwidth200 maxwidth500'); print '
'.$langs->trans('User').''.$person_name.'
'.$langs->trans('User').''; $selecteduser = !empty($id) ? $id : ''; - print $form->select_dolusers($selecteduser, 'user', 1, '', 0, '', '', (string) $object->entity, 0, 0, '', 0, '', 'minwidth200 maxwidth500'); + print $form->select_dolusers($selecteduser, 'user', 1, null, 0, '', '', (string) $object->entity, 0, 0, '', 0, '', 'minwidth200 maxwidth500'); print '
'.$langs->trans('User').''.$person_name.'
'.$langs->trans('User').''.($person_name ?? '').'
'.$langs->trans("NothingToDo")."
'; + print ''.$langs->trans('MigrationApiRestTokens')."
\n"; + + $error = 0; + $nbofmigration = 0; + $allexistingtokens = array(); + + $db->begin(); + + $sqlforalltokens = "SELECT oat.token"; + $sqlforalltokens .= " FROM ".$db->prefix()."oauth_token AS oat"; + $sqlforalltokens .= " WHERE oat.service = 'dolibarr_rest_api'"; + + $resalltoken = $db->query($sqlforalltokens); + + if ($resalltoken) { + while ($tokenobj = $db->fetch_object($resalltoken)) { + $allexistingtokens[] = dolDecrypt($tokenobj->token); + } + } else { + $error++; + dol_print_error($db); + $db->rollback(); + } + + if (!$error) { + $sql = "SELECT 'dolibarr_rest_api' AS service, u.api_key AS token, GROUP_CONCAT(rights.fk_id SEPARATOR ',') AS state, u.rowid AS fk_user, CURRENT_TIMESTAMP AS datec, rights.entity"; + $sql .= " FROM llx_user AS u"; + $sql .= " LEFT JOIN (SELECT fk_user, fk_id, entity FROM llx_user_rights UNION SELECT gu.fk_user, gr.fk_id, gr.entity FROM llx_usergroup_user AS gu JOIN llx_usergroup_rights AS gr on gu.fk_usergroup = gr.fk_usergroup) AS rights on rights.fk_user = u.rowid"; + $sql .= " WHERE u.api_key IS NOT NULL"; + $sql .= " AND rights.entity = 1"; + $sql .= " GROUP BY u.login, u.api_key, u.rowid, rights.entity"; + + $result = $db->query($sql); + + if ($result) { + while ($obj = $db->fetch_object($result)) { + if (!in_array(dolDecrypt($obj->token), $allexistingtokens)) { + $sqlforinsert = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token (service, token, state, fk_user, datec, entity)"; + $sqlforinsert .= " VALUES ('".$obj->service."', '".$obj->token."', '".$obj->state."', ".$obj->fk_user.", '".$obj->datec."', ".$obj->entity.")"; + + $insertresult = $db->query($sqlforinsert); + if (!$insertresult) { + $error++; + dol_print_error($db); + } else { + $nbofmigration++; + } + } + } + + if (!$error) { + $db->commit(); + } else { + $db->rollback(); + } + } else { + dol_print_error($db); + $db->rollback(); + } + } + + if (!$nbofmigration) { + print '
'.$langs->trans("NothingToDo")."
'; + print ''; + print img_object('', $picto, 'class="pictoobjectwidth paddingright"').' '.$objMod->getName(); + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + print ''; + print ''; + + print '
'; - print ''; - print img_object('', $picto, 'class="pictoobjectwidth paddingright"').' '.$objMod->getName(); - print ''; + print ''; print ''; - print ''; - print ''; + print ''; + print img_picto($langs->trans("AdministratorDesc"), 'star', 'class="paddingleft valignmiddle"'); print ''; + print ''; + print img_picto($langs->trans("Active"), 'switch_on', '', 0, 0, 0, '', 'opacitymedium'); print ''; + } + print ''; + print ''; + print 'id.'&confirm=yes">'; + //print img_edit_remove($langs->trans("Remove")); + print img_picto($langs->trans("Remove"), 'switch_on'); + print ''; print ''; + print img_picto($langs->trans("Active"), 'switch_on', '', 0, 0, 0, '', 'opacitymedium'); + print ''; - - print ''; - print ''; - - print '
'; - $htmltext = $langs->trans("ID").': '.$obj->id; - $htmltext .= '
'.$langs->trans("Permission").': user->hasRight(\''.dol_escape_htmltag($obj->module).'\', \''.dol_escape_htmltag($obj->perms).'\''.($obj->subperms ? ', \''.dol_escape_htmltag($obj->subperms).'\'' : '').')'; - print $form->textwithpicto('', $htmltext, 1, 'help', 'inline-block marginrightonly'); - //print ''.$obj->id.''; - print '
'.$langs->trans("UserHasNoPermissions").'
'; print '
'; From 7a498c0c917ae3ca690ba45b6c4d9437faa63913 Mon Sep 17 00:00:00 2001 From: yannis Date: Thu, 3 Jul 2025 17:13:39 +0200 Subject: [PATCH 053/387] feat: remove perms when removed group --- htdocs/user/class/user.class.php | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 5fb2bfb8d38..8bd7bd5dbad 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -3034,6 +3034,26 @@ class User extends CommonObject $this->db->begin(); + $idtodelete = array(); + + $sqlforid = "SELECT gr.fk_id"; + $sqlforid .= " FROM llx_usergroup_user AS gu"; + $sqlforid .= " JOIN llx_usergroup_rights AS gr ON gu.fk_usergroup = gr.fk_usergroup"; + $sqlforid .= " WHERE (gu.fk_user = ".((int) $this->id)." AND gu.fk_usergroup = ".((int) $group); + if (empty($entity)) { + $sqlforid .= " AND gu.entity IN (0, 1)"; + } else { + $sqlforid .= " AND gu.entity = ".((int) $entity); + } + $sqlforid .= ") AND NOT EXISTS(SELECT ur.fk_id FROM llx_user_rights as ur WHERE ur.entity = 1 AND ur.fk_user = ".((int) $this->id)." AND gr.fk_id = ur.fk_id)"; + + $idtodeletequery = $this->db->query($sqlforid); + if ($idtodeletequery) { + while ($idobj = $this->db->fetch_object($idtodeletequery)) { + $idtodelete [] = $idobj->fk_id; + } + } + $sql = "DELETE FROM ".$this->db->prefix()."usergroup_user"; $sql .= " WHERE fk_user = ".((int) $this->id); $sql .= " AND fk_usergroup = ".((int) $group); @@ -3045,6 +3065,34 @@ class User extends CommonObject $result = $this->db->query($sql); if ($result) { + if (!$error) { + $sqlusertokens = "SELECT oat.rowid, oat.state as rights"; + $sqlusertokens .= " FROM llx_oauth_token AS oat"; + $sqlusertokens .= " WHERE oat.fk_user = ".((int) $this->id); + $sqlusertokens .= " AND oat.service = 'dolibarr_rest_api'"; + + $idtodeletequery = $this->db->query($sqlforid); + $resulttokens = $this->db->query($sqlusertokens); + if ($resulttokens) { + var_dump($idtodelete); + + while ($obj = $this->db->fetch_object($resulttokens)) { + if (!empty($obj->rights)) { + $newtokenrigths = array_diff(explode(',', $obj->rights), $idtodelete); + + $sqlupdate = "UPDATE ".MAIN_DB_PREFIX."oauth_token"; + $sqlupdate.= " SET state = '".$this->db->escape(preg_replace('/\s+/', '', implode(',', $newtokenrigths)))."'"; + $sqlupdate.= " WHERE rowid = '".$obj->rowid."'"; + + $resupdate = $this->db->query($sqlupdate); + if (!$resupdate) { + $error++; + dol_print_error($this->db); + } + } + } + } + } if (!$error && !$notrigger) { $this->context = array('audit' => $langs->trans("UserRemovedFromGroup"), 'oldgroupid' => $group); From 0dce11a4f1adf4a2b4183a44ba00e2479205a5b2 Mon Sep 17 00:00:00 2001 From: yannis Date: Fri, 4 Jul 2025 09:35:42 +0200 Subject: [PATCH 054/387] feat: improve delrights that remove perms to tokens --- htdocs/user/class/user.class.php | 2 +- htdocs/user/class/usergroup.class.php | 74 ++++++++++++++++----------- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 8bd7bd5dbad..4e63a835b49 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -1280,7 +1280,7 @@ class User extends CommonObject if (!empty($wherefordel) && $wherefordel != 'allmodules') { $sqlforid .= " AND (".$wherefordel.")"; } - $sqlforid .= ") AND NOT EXISTS(SELECT gr.fk_id FROM llx_usergroup_rights as gr, llx_usergroup_user as gu WHERE gr.entity = 1 AND gu.entity IN (0,1) AND gr.fk_usergroup = gu.fk_usergroup AND gu.fk_user = 3 AND id = gr.fk_id)"; + $sqlforid .= ") AND NOT EXISTS(SELECT gr.fk_id FROM llx_usergroup_rights as gr, llx_usergroup_user as gu WHERE gr.entity = 1 AND gu.entity IN (0,1) AND gr.fk_usergroup = gu.fk_usergroup AND gu.fk_user = ".((int) $this->id)." AND id = gr.fk_id)"; $sqlusertokens = "SELECT oat.rowid, oat.state as rights"; $sqlusertokens .= " FROM llx_oauth_token AS oat"; diff --git a/htdocs/user/class/usergroup.class.php b/htdocs/user/class/usergroup.class.php index 698ee6c7758..2af8ad0dc1f 100644 --- a/htdocs/user/class/usergroup.class.php +++ b/htdocs/user/class/usergroup.class.php @@ -558,42 +558,56 @@ class UserGroup extends CommonObject } if (!$error) { - $idtodelete = array(); + // Get all users id that are in this group + $sqlforusers = "SELECT gu.fk_user as id"; + $sqlforusers .= " FROM llx_usergroup_user AS gu WHERE"; + $sqlforusers .= " gu.fk_usergroup = ".((int) $this->id); - // Query to avoid erasing perms from token if the user still have them himself - $sqlforid = "SELECT id"; - $sqlforid .= " FROM ".$this->db->prefix()."rights_def"; - $sqlforid .= " WHERE (entity = ".((int) $entity); - if (!empty($wherefordel) && $wherefordel != 'allmodules') { - $sqlforid .= " AND ".$wherefordel; - } - $sqlforid .= ") AND NOT EXISTS(SELECT ur.fk_id FROM llx_user_rights as ur WHERE ur.entity = 1 AND ur.fk_user = 3 AND id = ur.fk_id)"; + $resultusers = $this->db->query($sqlforusers); - $sqlusertokens = "SELECT oat.rowid, oat.state as rights"; - $sqlusertokens .= " FROM llx_usergroup_user AS gu"; - $sqlusertokens .= " JOIN llx_oauth_token AS oat ON gu.fk_user = oat.fk_user"; - $sqlusertokens .= " WHERE gu.fk_usergroup = ".((int) $this->id); - $sqlusertokens .= " AND oat.service = 'dolibarr_rest_api'"; + if ($resultusers) { + while ($usertocheck = $this->db->fetch_object($resultusers)) { + $idtodelete = array(); - $idtodeletequery = $this->db->query($sqlforid); - $resulttokens = $this->db->query($sqlusertokens); - if ($resulttokens && $idtodeletequery) { - while ($idobj = $this->db->fetch_object($idtodeletequery)) { - $idtodelete []= $idobj->id; - } + // Query to avoid erasing perms from token if the user still have them himself + $sqlforid = "SELECT id"; + $sqlforid .= " FROM ".$this->db->prefix()."rights_def"; + $sqlforid .= " WHERE (entity = ".((int) $entity); + if (!empty($wherefordel) && $wherefordel != 'allmodules') { + $sqlforid .= " AND ".$wherefordel; + } + $sqlforid .= ") AND NOT EXISTS(SELECT ur.fk_id FROM llx_user_rights as ur WHERE ur.entity = 1 AND ur.fk_user = ".((int) $usertocheck->id)." AND id = ur.fk_id)"; - while ($obj = $this->db->fetch_object($resulttokens)) { - if (!empty($obj->rights)) { - $newtokenrigths = array_diff(explode(',', $obj->rights), $idtodelete); + // Get all the tokens of the current user from query + $sqlusertokens = "SELECT oat.rowid, oat.state as rights"; + $sqlusertokens .= " FROM llx_usergroup_user AS gu"; + $sqlusertokens .= " JOIN llx_oauth_token AS oat ON gu.fk_user = oat.fk_user"; + $sqlusertokens .= " WHERE gu.fk_usergroup = ".((int) $this->id); + $sqlusertokens .= " AND oat.fk_user = ".((int) $usertocheck->id); + $sqlusertokens .= " AND oat.service = 'dolibarr_rest_api'"; - $sqlupdate = "UPDATE ".MAIN_DB_PREFIX."oauth_token"; - $sqlupdate.= " SET state = '".$this->db->escape(preg_replace('/\s+/', '', implode(',', $newtokenrigths)))."'"; - $sqlupdate.= " WHERE rowid = '".$obj->rowid."'"; + $idtodeletequery = $this->db->query($sqlforid); + $resulttokens = $this->db->query($sqlusertokens); + if ($resulttokens && $idtodeletequery) { + while ($idobj = $this->db->fetch_object($idtodeletequery)) { + $idtodelete []= $idobj->id; + } - $resupdate = $this->db->query($sqlupdate); - if (!$resupdate) { - $error++; - dol_print_error($this->db); + while ($obj = $this->db->fetch_object($resulttokens)) { + if (!empty($obj->rights)) { + var_dump($obj->rights, $idtodelete); + $newtokenrigths = array_diff(explode(',', $obj->rights), $idtodelete); + + $sqlupdate = "UPDATE ".MAIN_DB_PREFIX."oauth_token"; + $sqlupdate.= " SET state = '".$this->db->escape(preg_replace('/\s+/', '', implode(',', $newtokenrigths)))."'"; + $sqlupdate.= " WHERE rowid = '".$obj->rowid."'"; + + $resupdate = $this->db->query($sqlupdate); + if (!$resupdate) { + $error++; + dol_print_error($this->db); + } + } } } } From e6ab4caf506961b934724f7e0b2dde3215720353 Mon Sep 17 00:00:00 2001 From: yannis Date: Fri, 4 Jul 2025 09:39:37 +0200 Subject: [PATCH 055/387] fix: trans --- htdocs/langs/en_US/users.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/langs/en_US/users.lang b/htdocs/langs/en_US/users.lang index 74bd2b7e848..1513ba59af8 100644 --- a/htdocs/langs/en_US/users.lang +++ b/htdocs/langs/en_US/users.lang @@ -144,7 +144,7 @@ CloneCategoriesUser=Clone the user's categories ConfirmUserClone=Are you sure you want to clone the user: %s? NewEmailUserClone=Email address of the new user SocialNetworksUser=Social networks for user -ApiToken=Api token +ApiToken=API token ListOfTokensForUser=List of tokens for this user ListOfTokensForAllUsers=List of tokens for all users ListOfRightsForToken=List of rights for this token From 3d65bb7ed60f1e9cb57ce56ac1d79bbf00883766 Mon Sep 17 00:00:00 2001 From: yannis Date: Fri, 4 Jul 2025 09:44:32 +0200 Subject: [PATCH 056/387] fix: precommit --- htdocs/api/admin/token_list.php | 1 - htdocs/user/api_token/card.php | 2 +- htdocs/user/api_token/list.php | 1 - htdocs/user/class/user.class.php | 1 + 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php index 732c7f686c3..f9c5d434281 100644 --- a/htdocs/api/admin/token_list.php +++ b/htdocs/api/admin/token_list.php @@ -320,7 +320,6 @@ print_barre_liste($langs->trans("ListOfTokensForAllUsers"), $page, $_SERVER["PHP include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php'; if (empty($reshook)) { - print ''; print ''; diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 489f5a6a2b4..2db34acd9f4 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -667,7 +667,7 @@ if ($action == 'create') { $sql = "SELECT r.id, r.libelle as label, r.module, r.perms, r.subperms, r.module_position, r.bydefault"; $sql .= " FROM ".MAIN_DB_PREFIX."rights_def as r"; $sql .= " WHERE r.libelle NOT LIKE 'tou%'"; // On ignore droits "tous" - $sql .= " AND r.entity = ".((int)$entity); + $sql .= " AND r.entity = ".((int) $entity); $sql .= " AND r.id IN (".implode(', ', $allusersperms).")"; if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index 0b2df474245..e4aa3b08ef3 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -396,7 +396,6 @@ include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php'; //print $hookmanager->resPrint; if (empty($reshook)) { - print ''; print '
'; diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 4e63a835b49..1b40136901a 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -1677,6 +1677,7 @@ class User extends CommonObject * * @param string $moduletag Limit permission for a particular module ('' by default means load all permissions) * @param int $forcereload Force reload of permissions even if they were already loaded (ignore cache) + * @param string $token Load only permissions of the token * @return void * @deprecated Use loadRights * From 18068419debe4b850724fd8bf1cfef66b8aa651a Mon Sep 17 00:00:00 2001 From: yannis Date: Fri, 4 Jul 2025 09:54:34 +0200 Subject: [PATCH 057/387] fix: php phan/stan --- htdocs/api/admin/token_list.php | 5 ++++- htdocs/user/api_token/card.php | 2 +- htdocs/user/api_token/list.php | 5 ++++- htdocs/user/class/user.class.php | 1 - htdocs/user/class/usergroup.class.php | 1 - 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php index f9c5d434281..ad9214a72e4 100644 --- a/htdocs/api/admin/token_list.php +++ b/htdocs/api/admin/token_list.php @@ -298,7 +298,10 @@ $head = api_admin_prepare_head(); print dol_get_fiche_head($head, 'token_list', '', -1); -$arrayofmassactions['predelete'] = img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete"); +$arrayofmassactions = array( + 'predelete' => img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete") +); + if (GETPOSTINT('nomassaction') || in_array($massaction, array('presend', 'predelete'))) { $arrayofmassactions = array(); } diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 2db34acd9f4..e9e4f9534c9 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -286,7 +286,7 @@ if (empty($reshook)) { } else { dol_print_error($db); } - if (!empty($nbtotalofrecords) && $nbtotalofrecords > 0) { + if (!isset($nbtotalofrecords) || $nbtotalofrecords > 0) { setEventMessages($langs->trans("ErrorFieldExist", $langs->transnoentitiesnoconv("ApiToken")), null, 'errors'); $action = 'create'; $error++; diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index e4aa3b08ef3..050de4ef6e1 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -365,7 +365,10 @@ print dol_get_fiche_end(); print ''."\n"; -$arrayofmassactions['predelete'] = img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete"); +$arrayofmassactions = array( + 'predelete' => img_picto('', 'delete', 'class="pictofixedwidth"').$langs->trans("Delete") +); + if (GETPOSTINT('nomassaction') || in_array($massaction, array('presend', 'predelete'))) { $arrayofmassactions = array(); } diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 1b40136901a..b859959bf0c 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -3075,7 +3075,6 @@ class User extends CommonObject $idtodeletequery = $this->db->query($sqlforid); $resulttokens = $this->db->query($sqlusertokens); if ($resulttokens) { - var_dump($idtodelete); while ($obj = $this->db->fetch_object($resulttokens)) { if (!empty($obj->rights)) { diff --git a/htdocs/user/class/usergroup.class.php b/htdocs/user/class/usergroup.class.php index 2af8ad0dc1f..77e6e8e1d11 100644 --- a/htdocs/user/class/usergroup.class.php +++ b/htdocs/user/class/usergroup.class.php @@ -595,7 +595,6 @@ class UserGroup extends CommonObject while ($obj = $this->db->fetch_object($resulttokens)) { if (!empty($obj->rights)) { - var_dump($obj->rights, $idtodelete); $newtokenrigths = array_diff(explode(',', $obj->rights), $idtodelete); $sqlupdate = "UPDATE ".MAIN_DB_PREFIX."oauth_token"; From c2ab33cfae1f1f1f8ad027b1c4fecba0d96c39a5 Mon Sep 17 00:00:00 2001 From: yannis Date: Fri, 4 Jul 2025 10:17:00 +0200 Subject: [PATCH 058/387] fix: precommit --- htdocs/user/class/user.class.php | 1 - 1 file changed, 1 deletion(-) diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index b859959bf0c..be47f479ffc 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -3075,7 +3075,6 @@ class User extends CommonObject $idtodeletequery = $this->db->query($sqlforid); $resulttokens = $this->db->query($sqlusertokens); if ($resulttokens) { - while ($obj = $this->db->fetch_object($resulttokens)) { if (!empty($obj->rights)) { $newtokenrigths = array_diff(explode(',', $obj->rights), $idtodelete); From dcd4049a9afe1046581645a4a3086eb445055c41 Mon Sep 17 00:00:00 2001 From: yannis Date: Fri, 4 Jul 2025 11:11:46 +0200 Subject: [PATCH 059/387] fix: precommit --- htdocs/user/class/user.class.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index be47f479ffc..33f6e35c9b4 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -1467,9 +1467,9 @@ class User extends CommonObject } else { // On table r=rights_def, the unique key is (id, entity) because id is hard coded into module descriptor and inserted during module activation. // So we must include the filter on entity on both table r. and ur. - $sql .= " AND r.entity = " . ((int)$conf->entity) . " AND ur.entity = " . ((int)$conf->entity); + $sql .= " AND r.entity = " . ((int) $conf->entity) . " AND ur.entity = " . ((int) $conf->entity); } - $sql .= " AND ur.fk_user = " . ((int)$this->id); + $sql .= " AND ur.fk_user = " . ((int) $this->id); $sql .= " AND r.perms IS NOT NULL"; if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled @@ -1530,19 +1530,19 @@ class User extends CommonObject if (isModEnabled('multicompany') && getDolGlobalString('MULTICOMPANY_TRANSVERSE_MODE')) { $sql .= " AND gu.entity IN (0," . $conf->entity . ")"; } else { - $sql .= " AND r.entity = " . ((int)$conf->entity); + $sql .= " AND r.entity = " . ((int) $conf->entity); } } else { - $sql .= " AND gr.entity = " . ((int)$conf->entity); // Only groups created in current entity + $sql .= " AND gr.entity = " . ((int) $conf->entity); // Only groups created in current entity // The entity on the table gu=usergroup_user should be useless and should never be used because it is already into gr and r. // but when using MULTICOMPANY_TRANSVERSE_MODE, we may have inserted record that make rubbish result here due to the duplicate record of // other entities, so we are forced to add a filter on gu here $sql .= " AND gu.entity IN (0," . $conf->entity . ")"; - $sql .= " AND r.entity = " . ((int)$conf->entity); // Only permission of modules enabled in current entity + $sql .= " AND r.entity = " . ((int) $conf->entity); // Only permission of modules enabled in current entity } // End of strange business rule $sql .= " AND gr.fk_usergroup = gu.fk_usergroup"; - $sql .= " AND gu.fk_user = " . ((int)$this->id); + $sql .= " AND gu.fk_user = " . ((int) $this->id); $sql .= " AND r.perms IS NOT NULL"; if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled From fab6619cbb27421321486ff046e7f55222bec777 Mon Sep 17 00:00:00 2001 From: yannis Date: Fri, 4 Jul 2025 14:15:12 +0200 Subject: [PATCH 060/387] fix: change only tokens in related entity --- htdocs/user/class/user.class.php | 1 + htdocs/user/class/usergroup.class.php | 1 + 2 files changed, 2 insertions(+) diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 33f6e35c9b4..8d2acdbad40 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -1286,6 +1286,7 @@ class User extends CommonObject $sqlusertokens .= " FROM llx_oauth_token AS oat"; $sqlusertokens .= " WHERE oat.fk_user = ".((int) $this->id); $sqlusertokens .= " AND oat.service = 'dolibarr_rest_api'"; + $sqlusertokens .= " AND oat.entity = ".$entity; $idtodeletequery = $this->db->query($sqlforid); $resulttokens = $this->db->query($sqlusertokens); diff --git a/htdocs/user/class/usergroup.class.php b/htdocs/user/class/usergroup.class.php index 77e6e8e1d11..4ae7f288a6e 100644 --- a/htdocs/user/class/usergroup.class.php +++ b/htdocs/user/class/usergroup.class.php @@ -585,6 +585,7 @@ class UserGroup extends CommonObject $sqlusertokens .= " WHERE gu.fk_usergroup = ".((int) $this->id); $sqlusertokens .= " AND oat.fk_user = ".((int) $usertocheck->id); $sqlusertokens .= " AND oat.service = 'dolibarr_rest_api'"; + $sqlusertokens .= " AND oat.entity = ".((int) $entity); $idtodeletequery = $this->db->query($sqlforid); $resulttokens = $this->db->query($sqlusertokens); From 55fc93fdee81e8e9de4430d8a904661b97d2f384 Mon Sep 17 00:00:00 2001 From: yannis Date: Fri, 4 Jul 2025 14:43:17 +0200 Subject: [PATCH 061/387] fix: showing perms of other entity --- htdocs/user/api_token/card.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index e9e4f9534c9..e0b58022ea8 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -639,13 +639,14 @@ if ($action == 'create') { // Users perms $sql = "SELECT ur.fk_id"; $sql .= " FROM ".MAIN_DB_PREFIX."user_rights as ur"; - $sql .= " WHERE ur.entity = ".((int) $entity); + $sql .= " WHERE ur.entity = ".((int) $token->entity); $sql .= " AND ur.fk_user = ".((int) $object->id); $sql .= " UNION "; // Groups perms $sql .= "SELECT gr.fk_id"; $sql .= " FROM ".MAIN_DB_PREFIX."usergroup_rights as gr"; - $sql .= " WHERE EXISTS(SELECT gu.rowid FROM llx_usergroup_user as gu WHERE gu.fk_user = ".((int) $id)." AND gu.fk_usergroup = gr.fk_usergroup)"; + $sql .= " WHERE gr.entity = ".((int) $token->entity); + $sql .= " AND EXISTS(SELECT gu.rowid FROM llx_usergroup_user as gu WHERE gu.fk_user = ".((int) $id)." AND gu.fk_usergroup = gr.fk_usergroup)"; dol_syslog("get user perms", LOG_DEBUG); $result = $db->query($sql); From 8883c2e038b4e396b3e75a4113bb1c2955e8ae1c Mon Sep 17 00:00:00 2001 From: yannis Date: Fri, 4 Jul 2025 14:50:07 +0200 Subject: [PATCH 062/387] fix: showing bad entity --- htdocs/user/api_token/card.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index e0b58022ea8..1ca68e5e36e 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -464,7 +464,7 @@ if ($action == 'create') { // Entity if (isModEnabled('multicompany') && is_object($mc)) { - $mc->getInfo($conf->entity); + $mc->getInfo($token->entity); print ''; print ''; } @@ -491,7 +493,7 @@ if ($action == 'create') { print '
'.$langs->trans("Entity").''; print ''; From 73ac786cbc2fc1e70eaa073a73e0a6057380cc55 Mon Sep 17 00:00:00 2001 From: yannis Date: Fri, 4 Jul 2025 16:06:48 +0200 Subject: [PATCH 063/387] feat: show all perms but disable button for not owned --- htdocs/user/api_token/card.php | 47 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 1ca68e5e36e..2cba28f82d6 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -65,7 +65,7 @@ $toselect = GETPOST('toselect', 'array'); $canreaduser = ($user->admin || $user->hasRight("user", "user", "read")); $caneditfield = ((($user->id == $id) && $user->hasRight("user", "self", "write")) || (($user->id != $id) && $user->hasRight("user", "user", "write"))); -$caneditperms = ($user->admin || $user->hasRight("user", "user", "write")); +$canedittoken = ($user->admin || ($user->id == $id)); // Security check $socid = 0; @@ -99,6 +99,8 @@ $form = new Form($db); $formadmin = new FormAdmin($db); $token = $db->fetch_object($resql); +$entity = $conf->entity; + /* * Actions */ @@ -205,7 +207,7 @@ if (empty($reshook)) { } } - if ($action == 'addrights' && $caneditperms && $confirm == 'yes') { + if ($action == 'addrights' && $canedittoken && $confirm == 'yes') { $tokenrigthsarray = []; if (!empty($token->rights)) { @@ -234,7 +236,7 @@ if (empty($reshook)) { $reloadtoken = true; } - if ($action == 'delrights' && $caneditperms && $confirm == 'yes') { + if ($action == 'delrights' && $canedittoken && $confirm == 'yes') { $tokenrigthsarray = explode(',', $token->rights); if (isset($rigthsarray)) { $tokenrigthsarray = array_diff($tokenrigthsarray, $rigthsarray); @@ -296,7 +298,7 @@ if (empty($reshook)) { $db->begin(); $sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token (service, token, fk_user, entity, datec)"; - $sql .= " VALUES ('dolibarr_rest_api', '".$db->escape(dolEncrypt($tokenstring, '', '', 'dolibarr'))."', ".($useridtoadd).", ".((int) $conf->entity).", '".$db->idate(dol_now())."')"; + $sql .= " VALUES ('dolibarr_rest_api', '".$db->escape(dolEncrypt($tokenstring, '', '', 'dolibarr'))."', ".($useridtoadd).", ".((int) $entity).", '".$db->idate(dol_now())."')"; $resql = $db->query($sql); if (!$resql) { @@ -385,7 +387,7 @@ if ($action == 'create') { } if (isModEnabled('multicompany') && is_object($mc)) { - $mc->getInfo($conf->entity); + $mc->getInfo($entity); print '
'.$langs->trans('Entity').''.$mc->label.'
'; print '
'; - print dolGetButtonAction($langs->trans('Delete'), '', 'delete', $_SERVER["PHP_SELF"].'?id='.$object->id.'&tokenid='.$token->token_id.'&action=delete&token='.newToken(), '', $caneditperms); + print dolGetButtonAction($langs->trans('Delete'), '', 'delete', $_SERVER["PHP_SELF"].'?id='.$object->id.'&tokenid='.$token->token_id.'&action=delete&token='.newToken(), '', $canedittoken); print '
'; print '
'; @@ -499,8 +501,6 @@ if ($action == 'create') { print load_fiche_titre($langs->trans("ListOfRightsForToken"), '', ''); - - // TODO : Rights part print ''."\n"; if ($user->admin) { @@ -553,11 +553,11 @@ if ($action == 'create') { print ''; print ''.$langs->trans("Module").''; - if ($caneditperms) { + if ($canedittoken) { print ''; - print ''.$langs->trans("All").""; + print ''.$langs->trans("All").""; print ' / '; - print ''.$langs->trans("None").""; + print ''.$langs->trans("None").""; print ''; } else { print ''; @@ -669,7 +669,6 @@ if ($action == 'create') { $sql .= " FROM ".MAIN_DB_PREFIX."rights_def as r"; $sql .= " WHERE r.libelle NOT LIKE 'tou%'"; // On ignore droits "tous" $sql .= " AND r.entity = ".((int) $entity); - $sql .= " AND r.id IN (".implode(', ', $allusersperms).")"; if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { $sql .= " AND r.perms NOT LIKE '%_advance'"; // Hide advanced perms if option is not enabled } @@ -758,13 +757,13 @@ if ($action == 'create') { print ''; // Permission and tick (2 columns) - if (($caneditperms && empty($objMod->rights_admin_allowed)) || empty($object->admin)) { - if ($caneditperms) { + if (($canedittoken && empty($objMod->rights_admin_allowed)) || empty($object->admin)) { + if ($canedittoken) { print ''; print ''; print ''; print ''; @@ -774,7 +773,7 @@ if ($action == 'create') { print ''; } } else { - if ($caneditperms) { + if ($canedittoken) { print ''; print ''; print ''; @@ -812,7 +811,7 @@ if ($action == 'create') { // Permission and tick (2 columns) if (!empty($object->admin) && !empty($objMod->rights_admin_allowed)) { // Permission granted because admin print ''; - if ($caneditperms) { + if ($canedittoken) { print ''; print img_picto($langs->trans("AdministratorDesc"), 'star', 'class="paddingleft valignmiddle"'); print ''; @@ -825,9 +824,9 @@ if ($action == 'create') { print ''; } elseif (in_array($obj->id, $tokenperms)) { // Permission granted by user print ''; - if ($caneditperms) { + if ($canedittoken) { print ''; - print 'id.'&confirm=yes">'; + print 'id.'&confirm=yes">'; //print img_edit_remove($langs->trans("Remove")); print img_picto($langs->trans("Remove"), 'switch_on'); print ''; @@ -851,9 +850,9 @@ if ($action == 'create') { print ''; } else { // Do not own permission - if ($caneditperms) { + if ($canedittoken && in_array($obj->id, $allusersperms)) { print ''; - print 'id.'&confirm=yes&token='.newToken().'">'; + print 'id.'&confirm=yes&token='.newToken().'">'; //print img_edit_add($langs->trans("Add")); print img_picto($langs->trans("Add"), 'switch_off'); print ''; @@ -869,9 +868,9 @@ if ($action == 'create') { } else { // Do not own permission print ''; - if ($caneditperms) { + if ($canedittoken && in_array($obj->id, $allusersperms)) { print ''; - print 'id.'&confirm=yes&token='.newToken().'">'; + print 'id.'&confirm=yes&token='.newToken().'">'; //print img_edit_add($langs->trans("Add")); print img_picto($langs->trans("Add"), 'switch_off'); print ''; From d720fa80ff21120703e06ffacad30c3f4f236ca8 Mon Sep 17 00:00:00 2001 From: yannis Date: Fri, 4 Jul 2025 16:38:02 +0200 Subject: [PATCH 064/387] fix: php phan/stan --- htdocs/user/api_token/card.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 2cba28f82d6..d342a0e358f 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -287,8 +287,10 @@ if (empty($reshook)) { $nbtotalofrecords = $objforcount->nbtotalofrecords; } else { dol_print_error($db); + $error++; } - if (!isset($nbtotalofrecords) || $nbtotalofrecords > 0) { + + if (isset($nbtotalofrecords) && $nbtotalofrecords > 0) { setEventMessages($langs->trans("ErrorFieldExist", $langs->transnoentitiesnoconv("ApiToken")), null, 'errors'); $action = 'create'; $error++; From 0cc77bb35de993e00118f46bf68ea1d023425cad Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 7 Jul 2025 10:39:50 +0200 Subject: [PATCH 065/387] feat: disable token tab if api mod not enabled --- htdocs/core/lib/usergroups.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/lib/usergroups.lib.php b/htdocs/core/lib/usergroups.lib.php index d1b3430df05..ec4541d5ce0 100644 --- a/htdocs/core/lib/usergroups.lib.php +++ b/htdocs/core/lib/usergroups.lib.php @@ -159,7 +159,7 @@ function user_prepare_head(User $object) $h++; } - if (!empty($object->api_key)) { + if (isModEnabled('api') && !empty($object->api_key)) { $head[$h][0] = DOL_URL_ROOT.'/user/api_token/list.php?id='.$object->id; $head[$h][1] = $langs->trans("ApiToken"); $head[$h][2] = 'apitoken'; From d9892a907c0df2aa35abbffe7596a3edaf8521fa Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 7 Jul 2025 10:57:12 +0200 Subject: [PATCH 066/387] feat: improve display of user in admin token list --- htdocs/api/admin/token_list.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php index ad9214a72e4..11d98358273 100644 --- a/htdocs/api/admin/token_list.php +++ b/htdocs/api/admin/token_list.php @@ -203,12 +203,11 @@ if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { $db->free($resql); } -$sql = "SELECT oat.rowid as token_id, oat.token, oat.entity, oat.state as rights, oat.fk_user as user_id, u.login as user, oat.datec as date_creation, oat.tms as date_modification"; +$sql = "SELECT oat.rowid as token_id, oat.token, oat.entity, oat.state as rights, oat.fk_user, oat.datec as date_creation, oat.tms as date_modification"; if (isModEnabled('multicompany')) { $sql .= ", e.label as entity_name"; } $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; -$sql .= " JOIN llx_user as u ON u.rowid = oat.fk_user"; if (isModEnabled('multicompany')) { $sql .= " JOIN ".$db->prefix()."entity as e ON oat.entity = e.rowid"; } @@ -432,6 +431,8 @@ if (empty($reshook)) { while ($i < $imaxinloop) { // Compute number of perms $obj = $db->fetch_object($resql); + $currentuser = new User($db); + $currentuser->fetch($obj->fk_user); $numperms = 0; if (!empty($obj->rights)) { $numperms = count(explode(",", $obj->rights)); @@ -450,13 +451,13 @@ if (empty($reshook)) { print ''; } print ''; - print ''; + print ''; print dolDecrypt($obj->token); print ''; print ''; print ''; - print ''; - print $obj->user; + print ''; + print $currentuser->getNomUrl(1); print ''; print ''; if (isModEnabled('multicompany')) { From 2d7526a53f30385c7ade5ac36b6627c2079a3894 Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 7 Jul 2025 11:06:56 +0200 Subject: [PATCH 067/387] feat: remove unecessary url param --- htdocs/api/admin/token_list.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php index 11d98358273..b38d9cb3f30 100644 --- a/htdocs/api/admin/token_list.php +++ b/htdocs/api/admin/token_list.php @@ -310,7 +310,7 @@ $morehtmlright = ''; $tmpurlforbutton = DOL_URL_ROOT.'/user/api_token/card.php?action=create&backtopage='.urlencode(DOL_URL_ROOT.'/api/admin/token_list.php'); $morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton); -print '
'; print ''; print ''; print ''; From 366b0f231e4d7a8129e9243a6c16c0621d970314 Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 7 Jul 2025 11:39:15 +0200 Subject: [PATCH 068/387] feat: disable token filter in lists --- htdocs/api/admin/token_list.php | 31 ++++++++++--------------------- htdocs/user/api_token/list.php | 31 ++++++++++--------------------- 2 files changed, 20 insertions(+), 42 deletions(-) diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php index b38d9cb3f30..bfebaa8f816 100644 --- a/htdocs/api/admin/token_list.php +++ b/htdocs/api/admin/token_list.php @@ -59,7 +59,6 @@ $confirm = GETPOST('confirm', 'alpha'); $toselect = GETPOST('toselect', 'array'); // List filters -$search_token = GETPOST('search_token', 'alpha'); $search_user = GETPOST('search_user', 'alpha'); $search_entity = GETPOST('search_entity', 'alpha'); $search_datec_startday = GETPOSTINT('search_datec_startday'); @@ -92,14 +91,13 @@ $pageprev = $page - 1; $pagenext = $page + 1; if (!$sortfield) { - $sortfield = 'oat.token'; + $sortfield = 'oat.tms'; } if (!$sortorder) { $sortorder = 'DESC'; } $arrayfields = array( - 'oat.token' => array('label' => "ApiToken", 'checked' => '1'), 'u.login' => array('label' => "User", 'checked' => '1'), 'e.label' => array('label' => "Entity", 'checked' => '1'), 'oat.datec' => array('label' => "DateCreation", 'checked' => '1'), @@ -110,7 +108,6 @@ $arrayfields = array( * Action */ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers - $search_token = ''; $search_user = ''; $search_entity = ''; $search_datec_startday = ''; @@ -203,7 +200,7 @@ if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { $db->free($resql); } -$sql = "SELECT oat.rowid as token_id, oat.token, oat.entity, oat.state as rights, oat.fk_user, oat.datec as date_creation, oat.tms as date_modification"; +$sql = "SELECT oat.rowid, oat.token, oat.entity, oat.state as rights, oat.fk_user, oat.datec as date_creation, oat.tms as date_modification"; if (isModEnabled('multicompany')) { $sql .= ", e.label as entity_name"; } @@ -215,9 +212,6 @@ $sql .= " WHERE service = 'dolibarr_rest_api'"; if (!isModEnabled('multicompany') || $conf->entity > 1) { $sql .= " AND oat.entity IN (".$conf->entity.")"; } -if ($search_token) { - $sql .= natural_search('oat.token', $search_token); -} if ($search_user) { $sql .= natural_search('u.login', $search_user); } @@ -336,11 +330,8 @@ if (empty($reshook)) { } // Token string - if (!empty($arrayfields['oat.token']['checked'])) { - print ''; - print ''; - print ''; - } + // We don't search out tokens because it is encrypted in database + print ''; // Entity if (!empty($arrayfields['u.login']['checked'])) { @@ -401,9 +392,7 @@ if (empty($reshook)) { print $form->showCheckAddButtons('checkforselect', 1); print ''; } - if (!empty($arrayfields['oat.token']['checked'])) { - print_liste_field_titre($arrayfields['oat.token']['label'], $_SERVER["PHP_SELF"], 'oat.token', '', $param, '', $sortfield, $sortorder); - } + print ''.$langs->trans("ApiToken").''; if (!empty($arrayfields['u.login']['checked'])) { print_liste_field_titre($arrayfields['u.login']['label'], $_SERVER["PHP_SELF"], 'u.login', '', $param, '', $sortfield, $sortorder); } @@ -443,15 +432,15 @@ if (empty($reshook)) { print ''; if ($massactionbutton || $massaction) { // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined $selected = 0; - if (in_array($obj->token_id, $arrayofselected)) { + if (in_array($obj->rowid, $arrayofselected)) { $selected = 1; } - print ''; + print ''; } print ''; } print ''; - print ''; + print ''; print dolDecrypt($obj->token); print ''; print ''; @@ -481,10 +470,10 @@ if (empty($reshook)) { print ''; if ($massactionbutton || $massaction) { $selected = 0; - if (in_array($obj->token_id, $arrayofselected)) { + if (in_array($obj->rowid, $arrayofselected)) { $selected = 1; } - print ''; + print ''; } print ''; } diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index 050de4ef6e1..7961d4350b1 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -59,7 +59,6 @@ $confirm = GETPOST('confirm', 'alpha'); $toselect = GETPOST('toselect', 'array'); // List filters -$search_token = GETPOST('search_token', 'alpha'); $search_entity = GETPOST('search_entity', 'alpha'); $search_datec_startday = GETPOSTINT('search_datec_startday'); $search_datec_startmonth = GETPOSTINT('search_datec_startmonth'); @@ -91,7 +90,7 @@ $pageprev = $page - 1; $pagenext = $page + 1; if (!$sortfield) { - $sortfield = 'oat.token'; + $sortfield = 'oat.tms'; } if (!$sortorder) { $sortorder = 'DESC'; @@ -116,7 +115,6 @@ if ($user->id != $id && !$canreaduser) { } $arrayfields = array( - 'oat.token' => array('label' => "ApiToken", 'checked' => '1'), 'e.label' => array('label' => "Entity", 'checked' => '1'), 'oat.datec' => array('label' => "DateCreation", 'checked' => '1'), 'oat.tms' => array('label' => "DateModification", 'checked' => '1'), @@ -141,7 +139,6 @@ if ($reshook < 0) { if (empty($reshook)) { if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers - $search_token = ''; $search_entity = ''; $search_datec_start = ''; $search_datec_end = ''; @@ -234,7 +231,7 @@ if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { $db->free($resql); } -$sql = "SELECT oat.rowid as token_id, oat.token, oat.entity, oat.state as rights, oat.datec as date_creation, oat.tms as date_modification"; +$sql = "SELECT oat.rowid, oat.token, oat.entity, oat.state as rights, oat.datec as date_creation, oat.tms as date_modification"; if (isModEnabled('multicompany')) { $sql .= ", e.label as entity_name"; } @@ -245,9 +242,6 @@ if (isModEnabled('multicompany')) { $sql .= " WHERE oat.fk_user = ".((int) $object->id); $sql .= " AND entity IN (".$conf->entity.")"; $sql .= " AND service = 'dolibarr_rest_api'"; -if ($search_token) { - $sql .= natural_search('oat.token', $search_token); -} if ($search_entity) { $sql .= natural_search('oat.entity', $search_entity); } @@ -413,11 +407,8 @@ if (empty($reshook)) { } // Token string - if (!empty($arrayfields['oat.token']['checked'])) { - print ''; - print ''; - print ''; - } + // We don't search out tokens because it is encrypted in database + print ''; // Entity if (!empty($arrayfields['e.label']['checked']) && isModEnabled('multicompany')) { @@ -471,9 +462,7 @@ if (empty($reshook)) { print $form->showCheckAddButtons('checkforselect', 1); print ''; } - if (!empty($arrayfields['oat.token']['checked'])) { - print_liste_field_titre($arrayfields['oat.token']['label'], $_SERVER["PHP_SELF"], 'oat.token', '', $param, '', $sortfield, $sortorder); - } + print ''.$langs->trans("ApiToken").''; if (!empty($arrayfields['e.label']['checked']) && isModEnabled('multicompany')) { print_liste_field_titre($arrayfields['e.label']['label'], $_SERVER["PHP_SELF"], 'e.label', '', $param, '', $sortfield, $sortorder); } @@ -508,15 +497,15 @@ if (empty($reshook)) { print ''; if ($massactionbutton || $massaction) { // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined $selected = 0; - if (in_array($obj->token_id, $arrayofselected)) { + if (in_array($obj->rowid, $arrayofselected)) { $selected = 1; } - print ''; + print ''; } print ''; } print ''; - print ''; + print ''; print dolDecrypt($obj->token); print ''; print ''; @@ -541,10 +530,10 @@ if (empty($reshook)) { print ''; if ($massactionbutton || $massaction) { $selected = 0; - if (in_array($obj->token_id, $arrayofselected)) { + if (in_array($obj->rowid, $arrayofselected)) { $selected = 1; } - print ''; + print ''; } print ''; } From 9af9bbfa3bfd9ece4db8144dca06d491d832273f Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 7 Jul 2025 12:14:32 +0200 Subject: [PATCH 069/387] feat: disable choosing token user for admin if creating from his list --- htdocs/user/api_token/card.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index d342a0e358f..a4f953ae66a 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -378,11 +378,10 @@ if ($action == 'create') { print ''; - if ($user->admin) { + if ($user->admin && empty($id)) { print ''; print ''; } else { print ''; From 625c323f76095826e5360e5cb8c2c47623de0fef Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 7 Jul 2025 14:20:24 +0200 Subject: [PATCH 070/387] feat: leave state field empty --- htdocs/install/upgrade2.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/htdocs/install/upgrade2.php b/htdocs/install/upgrade2.php index 53191853413..7230fa25d9a 100644 --- a/htdocs/install/upgrade2.php +++ b/htdocs/install/upgrade2.php @@ -5325,20 +5325,17 @@ function migrate_apiresttokens() } if (!$error) { - $sql = "SELECT 'dolibarr_rest_api' AS service, u.api_key AS token, GROUP_CONCAT(rights.fk_id SEPARATOR ',') AS state, u.rowid AS fk_user, CURRENT_TIMESTAMP AS datec, rights.entity"; + $sql = "SELECT 'dolibarr_rest_api' AS service, u.api_key AS token, u.rowid AS fk_user, CURRENT_TIMESTAMP AS datec, u.entity"; $sql .= " FROM llx_user AS u"; - $sql .= " LEFT JOIN (SELECT fk_user, fk_id, entity FROM llx_user_rights UNION SELECT gu.fk_user, gr.fk_id, gr.entity FROM llx_usergroup_user AS gu JOIN llx_usergroup_rights AS gr on gu.fk_usergroup = gr.fk_usergroup) AS rights on rights.fk_user = u.rowid"; $sql .= " WHERE u.api_key IS NOT NULL"; - $sql .= " AND rights.entity = 1"; - $sql .= " GROUP BY u.login, u.api_key, u.rowid, rights.entity"; $result = $db->query($sql); if ($result) { while ($obj = $db->fetch_object($result)) { if (!in_array(dolDecrypt($obj->token), $allexistingtokens)) { - $sqlforinsert = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token (service, token, state, fk_user, datec, entity)"; - $sqlforinsert .= " VALUES ('".$obj->service."', '".$obj->token."', '".$obj->state."', ".$obj->fk_user.", '".$obj->datec."', ".$obj->entity.")"; + $sqlforinsert = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token (service, token, fk_user, datec, entity)"; + $sqlforinsert .= " VALUES ('".$obj->service."', '".$obj->token."', ".$obj->fk_user.", '".$obj->datec."', ".$obj->entity.")"; $insertresult = $db->query($sqlforinsert); if (!$insertresult) { From f211cc832dd93c590cc0e98715070daaaf00bba8 Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 7 Jul 2025 15:54:52 +0200 Subject: [PATCH 071/387] feat: added tpl for token lists --- htdocs/api/admin/token_list.php | 177 +------------------- htdocs/core/tpl/apitoken_list.tpl.php | 227 ++++++++++++++++++++++++++ htdocs/user/api_token/card.php | 4 +- htdocs/user/api_token/list.php | 181 +------------------- 4 files changed, 240 insertions(+), 349 deletions(-) create mode 100644 htdocs/core/tpl/apitoken_list.tpl.php diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php index bfebaa8f816..884aa05eb70 100644 --- a/htdocs/api/admin/token_list.php +++ b/htdocs/api/admin/token_list.php @@ -315,182 +315,11 @@ print_barre_liste($langs->trans("ListOfTokensForAllUsers"), $page, $_SERVER["PHP include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php'; -if (empty($reshook)) { - print ''; - print '
'.$langs->trans('User').''; - $selecteduser = !empty($id) ? $id : ''; - print $form->select_dolusers($selecteduser, 'user', 1, null, 0, '', '', (string) $object->entity, 0, 0, '', 0, '', 'minwidth200 maxwidth500'); + print $form->select_dolusers('', 'user', 1, null, 0, '', '', (string) $object->entity, 0, 0, '', 0, '', 'minwidth200 maxwidth500'); print '
'.$langs->trans('User').''.($person_name ?? '').'
'; +$colspan = 6; // Base colspan for empty list - print ''; +include DOL_DOCUMENT_ROOT.'/core/tpl/apitoken_list.tpl.php'; - // Action buttons - if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { - print ''; - } - - // Token string - // We don't search out tokens because it is encrypted in database - print ''; - - // Entity - if (!empty($arrayfields['u.login']['checked'])) { - print ''; - } - - // Entity - if (!empty($arrayfields['e.label']['checked']) && isModEnabled('multicompany')) { - print ''; - } - - // Number of perms - // We don't search out number of perms because it is a string field, - // and we don't want to count into it with sql query - print ''; - - // Date creation - if (!empty($arrayfields['oat.datec']['checked'])) { - print ''; - } - - // Date modification - if (!empty($arrayfields['oat.tms']['checked'])) { - print ''; - } - - // Action buttons - if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { - print ''; - } - - print ""; - - print ''; - if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { - print ''; - } - print ''; - if (!empty($arrayfields['u.login']['checked'])) { - print_liste_field_titre($arrayfields['u.login']['label'], $_SERVER["PHP_SELF"], 'u.login', '', $param, '', $sortfield, $sortorder); - } - if (!empty($arrayfields['e.label']['checked']) && isModEnabled('multicompany')) { - print_liste_field_titre($arrayfields['e.label']['label'], $_SERVER["PHP_SELF"], 'e.label', '', $param, '', $sortfield, $sortorder); - } - print ''; - if (!empty($arrayfields['oat.datec']['checked'])) { - print_liste_field_titre($arrayfields['oat.datec']['label'], $_SERVER["PHP_SELF"], 'oat.datec', '', $param, '', $sortfield, $sortorder, 'center '); - } - if (!empty($arrayfields['oat.tms']['checked'])) { - print_liste_field_titre($arrayfields['oat.tms']['label'], $_SERVER["PHP_SELF"], 'oat.tms', '', $param, '', $sortfield, $sortorder, 'center '); - } - if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { - print ''; - } - print ''; - - // List of tokens of user - $i = 0; - $imaxinloop = ($limit ? min($num, $limit) : $num); - if ($num > 0) { - while ($i < $imaxinloop) { - // Compute number of perms - $obj = $db->fetch_object($resql); - $currentuser = new User($db); - $currentuser->fetch($obj->fk_user); - $numperms = 0; - if (!empty($obj->rights)) { - $numperms = count(explode(",", $obj->rights)); - } - print ''; - // Action column - if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { - print ''; - } - print ''; - print ''; - if (isModEnabled('multicompany')) { - print ''; - } - print ''; - print ''; - print ''; - if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { - print ''; - } - print ''; - $i++; - } - } else { - $colspan = 6; // Base colspan - if (isModEnabled('multicompany')) { - $colspan++; - } - print ''; - } - - print "
'; - $searchpicto = $form->showFilterButtons('left'); - print $searchpicto; - print ''; - print ''; - print ''; - print ''; - print ''; - print '
'; - print $form->selectDate($search_datec_start ? $search_datec_start : -1, 'search_datec_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); - print '
'; - print '
'; - print $form->selectDate($search_datec_end ? $search_datec_end : -1, 'search_datec_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('to')); - print '
'; - print '
'; - print '
'; - print $form->selectDate($search_tms_start ? $search_tms_start : -1, 'search_tms_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); - print '
'; - print '
'; - print $form->selectDate($search_tms_end ? $search_tms_end : -1, 'search_tms_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('to')); - print '
'; - print '
'; - $searchpicto = $form->showFilterButtons('left'); - print $searchpicto; - print '
'; - print $form->showCheckAddButtons('checkforselect', 1); - print ''.$langs->trans("ApiToken").''.$langs->trans("NumberOfPermissions").''; - print $form->showCheckAddButtons('checkforselect', 1); - print '
'; - if ($massactionbutton || $massaction) { // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined - $selected = 0; - if (in_array($obj->rowid, $arrayofselected)) { - $selected = 1; - } - print ''; - } - print ''; - print ''; - print dolDecrypt($obj->token); - print ''; - print ''; - print ''; - print $currentuser->getNomUrl(1); - print ''; - print ''; - print ''; - print ''; - print $obj->entity_name; - print ' '; - print ''; - print $numperms; - print ''; - print dol_print_date($db->jdate($obj->date_creation), 'day'); - print ''; - print dol_print_date($db->jdate($obj->date_modification), 'day'); - print ''; - if ($massactionbutton || $massaction) { - $selected = 0; - if (in_array($obj->rowid, $arrayofselected)) { - $selected = 1; - } - print ''; - } - print '
'.$langs->trans("None").'
"; - print ''; -} +print ''; llxFooter(); $db->close(); diff --git a/htdocs/core/tpl/apitoken_list.tpl.php b/htdocs/core/tpl/apitoken_list.tpl.php new file mode 100644 index 00000000000..06ffd0be61c --- /dev/null +++ b/htdocs/core/tpl/apitoken_list.tpl.php @@ -0,0 +1,227 @@ + + * Copyright (C) 2024 MDW + * 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 . + * or see https://www.gnu.org/ + */ + +/** + * @var CommonObject $object + * @var DoliDB $db + * @var Form $form + * @var Translate $langs + * @var string $search_user + * @var string $search_entity + * @var string $search_datec_start + * @var string $search_datec_end + * @var string $search_tms_start + * @var string $search_tms_end + * @var string $param + * @var string $sortfield + * @var string $sortorder + * @var int $limit + * @var mysqli_result $resql + * @var string $massactionbutton + * @var string $massaction + * @var array $arrayofselected + * @var int $colspan + * + * @var int $num + */ + +' +@phan-var-force Propal|Contrat|Commande|Facture|Expedition|Delivery|FactureFournisseur|FactureFournisseur|SupplierProposal $object +@phan-var-force int $num +'; + +echo "\n"; + + +print ''; +print ''; + +print ''; + +// Action buttons +if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; +} + +// Token string +// We don't search out tokens because it is encrypted in database +print ''; + +// User +if (!empty($arrayfields['u.login']['checked'])) { + print ''; +} + +// Entity +if (!empty($arrayfields['e.label']['checked']) && isModEnabled('multicompany')) { + print ''; +} + +// Number of perms +// We don't search out number of perms because it is a string field, +// and we don't want to count into it with sql query +print ''; + +// Date creation +if (!empty($arrayfields['oat.datec']['checked'])) { + print ''; +} + +// Date modification +if (!empty($arrayfields['oat.tms']['checked'])) { + print ''; +} + +// Action buttons +if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; +} + +print ""; + +print ''; +if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; +} +print ''; +if (!empty($arrayfields['u.login']['checked'])) { + print_liste_field_titre($arrayfields['u.login']['label'], $_SERVER["PHP_SELF"], 'u.login', '', $param, '', $sortfield, $sortorder); +} +if (!empty($arrayfields['e.label']['checked']) && isModEnabled('multicompany')) { + print_liste_field_titre($arrayfields['e.label']['label'], $_SERVER["PHP_SELF"], 'e.label', '', $param, '', $sortfield, $sortorder); +} +print ''; +if (!empty($arrayfields['oat.datec']['checked'])) { + print_liste_field_titre($arrayfields['oat.datec']['label'], $_SERVER["PHP_SELF"], 'oat.datec', '', $param, '', $sortfield, $sortorder, 'center '); +} +if (!empty($arrayfields['oat.tms']['checked'])) { + print_liste_field_titre($arrayfields['oat.tms']['label'], $_SERVER["PHP_SELF"], 'oat.tms', '', $param, '', $sortfield, $sortorder, 'center '); +} +if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; +} +print ''; + +// List of tokens of user +$i = 0; +$imaxinloop = ($limit ? min($num, $limit) : $num); +if ($num > 0) { + while ($i < $imaxinloop) { + // Compute number of perms + $obj = $db->fetch_object($resql); + $useridparam = isset($obj->fk_user) ? $obj->fk_user : $object->id; + $numperms = 0; + if (!empty($obj->rights)) { + $numperms = count(explode(",", $obj->rights)); + } + print ''; + // Action column + if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } + print ''; + if (!empty($arrayfields['u.login']['checked'])) { + $currentuser = new User($db); + $currentuser->fetch($obj->fk_user); + print ''; + } + if (isModEnabled('multicompany')) { + print ''; + } + print ''; + print ''; + print ''; + if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { + print ''; + } + print ''; + $i++; + } +} else { + if (isModEnabled('multicompany')) { + $colspan++; + } + print ''; +} + +print "
'; + $searchpicto = $form->showFilterButtons('left'); + print $searchpicto; + print ''; + print ''; + print ''; + print ''; + print ''; + print '
'; + print $form->selectDate($search_datec_start ? $search_datec_start : -1, 'search_datec_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); + print '
'; + print '
'; + print $form->selectDate($search_datec_end ? $search_datec_end : -1, 'search_datec_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('to')); + print '
'; + print '
'; + print '
'; + print $form->selectDate($search_tms_start ? $search_tms_start : -1, 'search_tms_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); + print '
'; + print '
'; + print $form->selectDate($search_tms_end ? $search_tms_end : -1, 'search_tms_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('to')); + print '
'; + print '
'; + $searchpicto = $form->showFilterButtons('left'); + print $searchpicto; + print '
'; + print $form->showCheckAddButtons('checkforselect', 1); + print ''.$langs->trans("ApiToken").''.$langs->trans("NumberOfPermissions").''; + print $form->showCheckAddButtons('checkforselect', 1); + print '
'; + if ($massactionbutton || $massaction) { // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined + $selected = 0; + if (in_array($obj->rowid, $arrayofselected)) { + $selected = 1; + } + print ''; + } + print ''; + print ''; + print dolDecrypt($obj->token); + print ''; + print ''; + print ''; + print $currentuser->getNomUrl(1); + print ''; + print ''; + print ''; + print ''; + print $obj->entity_name; + print ' '; + print ''; + print $numperms; + print ''; + print dol_print_date($db->jdate($obj->date_creation), 'dayhour'); + print ''; + print dol_print_date($db->jdate($obj->date_modification), 'dayhour'); + print ''; + if ($massactionbutton || $massaction) { + $selected = 0; + if (in_array($obj->rowid, $arrayofselected)) { + $selected = 1; + } + print ''; + } + print '
'.$langs->trans("None").'
"; diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index a4f953ae66a..ecfa0170fb6 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -481,14 +481,14 @@ if ($action == 'create') { // Creation date print ''.$langs->trans("DateCreation").''; print ''; - print dol_print_date($db->jdate($token->date_creation), 'day'); + print dol_print_date($db->jdate($token->date_creation), 'dayhour'); print ''; print ''."\n"; // Modification date print ''.$langs->trans("DateModification").''; print ''; - print dol_print_date($db->jdate($token->date_modification), 'day'); + print dol_print_date($db->jdate($token->date_modification), 'dayhour'); print ''; print ''."\n"; diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index 7961d4350b1..4fc0a736dce 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -266,8 +266,6 @@ $resql = $db->query($sql); $num = $db->num_rows($resql); -llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-user page-card_param_ihm'); - $param = '&id='.$id; // We always need the id of the user if ($limit > 0 && $limit != $conf->liste_limit) { $param .= '&limit='.((int) $limit); @@ -313,10 +311,6 @@ $arrayofselected = is_array($toselect) ? $toselect : array(); $head = user_prepare_head($object); -$title = $langs->trans("User"); - -print dol_get_fiche_head($head, 'apitoken', $title, -1, 'user'); - $linkback = ''.$langs->trans("BackToList").''; $morehtmlref = ''; @@ -326,6 +320,10 @@ $morehtmlref .= ''; $urltovirtualcard = '/user/virtualcard.php?id='.((int) $object->id); $morehtmlref .= dolButtonToOpenUrlInDialogPopup('publicvirtualcard', $langs->transnoentitiesnoconv("PublicVirtualCardUrl").' - '.$object->getFullName($langs), img_picto($langs->trans("PublicVirtualCardUrl"), 'card', 'class="valignmiddle marginleftonly paddingrightonly"'), $urltovirtualcard, '', 'nohover'); +llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-user page-card_param_ihm'); + +print dol_get_fiche_head($head, 'apitoken', $langs->trans("User"), -1, 'user'); + dol_banner_tab($object, 'id', $linkback, $user->hasRight("user", "user", "read") || $user->admin, 'rowid', 'ref', $morehtmlref); print '
'; @@ -368,12 +366,8 @@ if (GETPOSTINT('nomassaction') || in_array($massaction, array('presend', 'predel } $massactionbutton = $form->selectMassAction('', $arrayofmassactions); -$morehtmlright = ''; -//if (!empty($moreoptions['showhideaddbutton']) && $conf->use_javascript_ajax) { $tmpurlforbutton = DOL_URL_ROOT.'/user/api_token/card.php?id='.$id.'&action=create'; -// TODO Permissions ? $morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton, '', $permtoeditline); -$morehtmlright .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton); -//} +$morehtmlright = dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton); print '
'."\n"; print ''; @@ -386,171 +380,12 @@ print_barre_liste($langs->trans("ListOfTokensForUser"), $page, $_SERVER["PHP_SEL include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php'; -// TODO : Build the hook management -// Other form for add user to group -//$parameters = array('caneditgroup' => $permissiontoeditgroup, 'groupslist' => $groupslist, 'exclude' => $exclude); -//$reshook = $hookmanager->executeHooks('formAddUserToGroup', $parameters, $object, $action); // Note that $action and $object may have been modified by hook -//print $hookmanager->resPrint; +$colspan = 5; // Base colspan for empty list -if (empty($reshook)) { - print ''; - print ''; +include DOL_DOCUMENT_ROOT.'/core/tpl/apitoken_list.tpl.php'; - print ''; - // Action buttons - if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { - print ''; - } - - // Token string - // We don't search out tokens because it is encrypted in database - print ''; - - // Entity - if (!empty($arrayfields['e.label']['checked']) && isModEnabled('multicompany')) { - print ''; - } - - // Number of perms - // We don't search out number of perms because it is a string field, - // and we don't want to count into it with sql query - print ''; - - // Date creation - if (!empty($arrayfields['oat.datec']['checked'])) { - print ''; - } - - // Date modification - if (!empty($arrayfields['oat.tms']['checked'])) { - print ''; - } - - // Action buttons - if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { - print ''; - } - - print ""; - - print ''; - if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { - print ''; - } - print ''; - if (!empty($arrayfields['e.label']['checked']) && isModEnabled('multicompany')) { - print_liste_field_titre($arrayfields['e.label']['label'], $_SERVER["PHP_SELF"], 'e.label', '', $param, '', $sortfield, $sortorder); - } - print ''; - if (!empty($arrayfields['oat.datec']['checked'])) { - print_liste_field_titre($arrayfields['oat.datec']['label'], $_SERVER["PHP_SELF"], 'oat.datec', '', $param, '', $sortfield, $sortorder, 'center '); - } - if (!empty($arrayfields['oat.tms']['checked'])) { - print_liste_field_titre($arrayfields['oat.tms']['label'], $_SERVER["PHP_SELF"], 'oat.tms', '', $param, '', $sortfield, $sortorder, 'center '); - } - if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { - print ''; - } - print ''; - - // List of tokens of user - $i = 0; - $imaxinloop = ($limit ? min($num, $limit) : $num); - if ($num > 0) { - while ($i < $imaxinloop) { - // Compute number of perms - $obj = $db->fetch_object($resql); - $numperms = 0; - if (!empty($obj->rights)) { - $numperms = count(explode(",", $obj->rights)); - } - print ''; - // Action column - if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { - print ''; - } - print ''; - if (isModEnabled('multicompany')) { - print ''; - } - print ''; - print ''; - print ''; - if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { - print ''; - } - print ''; - $i++; - } - } else { - $colspan = 5; // Base colspan - if (isModEnabled('multicompany')) { - $colspan++; - } - print ''; - } - - print "
'; - $searchpicto = $form->showFilterButtons('left'); - print $searchpicto; - print ''; - print ''; - print ''; - print '
'; - print $form->selectDate($search_datec_start ? $search_datec_start : -1, 'search_datec_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); - print '
'; - print '
'; - print $form->selectDate($search_datec_end ? $search_datec_end : -1, 'search_datec_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('to')); - print '
'; - print '
'; - print '
'; - print $form->selectDate($search_tms_start ? $search_tms_start : -1, 'search_tms_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('From')); - print '
'; - print '
'; - print $form->selectDate($search_tms_end ? $search_tms_end : -1, 'search_tms_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans('to')); - print '
'; - print '
'; - $searchpicto = $form->showFilterButtons('left'); - print $searchpicto; - print '
'; - print $form->showCheckAddButtons('checkforselect', 1); - print ''.$langs->trans("ApiToken").''.$langs->trans("NumberOfPermissions").''; - print $form->showCheckAddButtons('checkforselect', 1); - print '
'; - if ($massactionbutton || $massaction) { // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined - $selected = 0; - if (in_array($obj->rowid, $arrayofselected)) { - $selected = 1; - } - print ''; - } - print ''; - print ''; - print dolDecrypt($obj->token); - print ''; - print ''; - print ''; - print ''; - print $obj->entity_name; - print ' '; - print ''; - print $numperms; - print ''; - print dol_print_date($db->jdate($obj->date_creation), 'day'); - print ''; - print dol_print_date($db->jdate($obj->date_modification), 'day'); - print ''; - if ($massactionbutton || $massaction) { - $selected = 0; - if (in_array($obj->rowid, $arrayofselected)) { - $selected = 1; - } - print ''; - } - print '
'.$langs->trans("None").'
"; - print '
'; -} +print ''; // End of page llxFooter(); From c24877a641dbde1ee746fcccff482ecf8351f486 Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 7 Jul 2025 15:59:42 +0200 Subject: [PATCH 072/387] feat: add link back to list of token and to list of users --- htdocs/langs/en_US/users.lang | 1 + htdocs/user/api_token/card.php | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/htdocs/langs/en_US/users.lang b/htdocs/langs/en_US/users.lang index 1513ba59af8..f9725646f3b 100644 --- a/htdocs/langs/en_US/users.lang +++ b/htdocs/langs/en_US/users.lang @@ -153,3 +153,4 @@ DeleteToken=Delete token ConfirmDeleteToken=Are you sure you want to delete this token? ListOfTokens=List of tokens NewToken=New token +BackToTokenList=Back to token list diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index ecfa0170fb6..f055cc85831 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -420,7 +420,8 @@ if ($action == 'create') { $tokenvalue = dolDecrypt($token->token); - $linkback = ''.$langs->trans("BackToList").''; + $linkback = ''.$langs->trans("BackToTokenList").''; + $linkback .= ''.$langs->trans("BackToList").''; $morehtmlref = ''; $morehtmlref .= img_picto($langs->trans("Download").' '.$langs->trans("VCard"), 'vcard.png', 'class="valignmiddle marginleftonly paddingrightonly"'); From 2707e0660e8d968ae41a59bd11104082ff7cae0e Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 7 Jul 2025 16:12:24 +0200 Subject: [PATCH 073/387] feat: improve tab titles and trans --- htdocs/core/lib/usergroups.lib.php | 2 +- htdocs/core/tpl/apitoken_list.tpl.php | 2 +- htdocs/langs/en_US/main.lang | 1 + htdocs/langs/en_US/users.lang | 2 +- htdocs/user/api_token/card.php | 6 +++--- htdocs/user/api_token/list.php | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/htdocs/core/lib/usergroups.lib.php b/htdocs/core/lib/usergroups.lib.php index ec4541d5ce0..f9b74c9a670 100644 --- a/htdocs/core/lib/usergroups.lib.php +++ b/htdocs/core/lib/usergroups.lib.php @@ -161,7 +161,7 @@ function user_prepare_head(User $object) if (isModEnabled('api') && !empty($object->api_key)) { $head[$h][0] = DOL_URL_ROOT.'/user/api_token/list.php?id='.$object->id; - $head[$h][1] = $langs->trans("ApiToken"); + $head[$h][1] = $langs->trans("ApiTokens"); $head[$h][2] = 'apitoken'; $h++; } diff --git a/htdocs/core/tpl/apitoken_list.tpl.php b/htdocs/core/tpl/apitoken_list.tpl.php index 06ffd0be61c..8936d607600 100644 --- a/htdocs/core/tpl/apitoken_list.tpl.php +++ b/htdocs/core/tpl/apitoken_list.tpl.php @@ -126,7 +126,7 @@ if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { print $form->showCheckAddButtons('checkforselect', 1); print ''; } -print ''.$langs->trans("ApiToken").''; +print ''.$langs->trans("Token").''; if (!empty($arrayfields['u.login']['checked'])) { print_liste_field_titre($arrayfields['u.login']['label'], $_SERVER["PHP_SELF"], 'u.login', '', $param, '', $sortfield, $sortorder); } diff --git a/htdocs/langs/en_US/main.lang b/htdocs/langs/en_US/main.lang index 4dcba6d1c45..7e50d028a68 100644 --- a/htdocs/langs/en_US/main.lang +++ b/htdocs/langs/en_US/main.lang @@ -1367,3 +1367,4 @@ ClickOnPlusToCreateOne=Click the "Plus" button to add one. Free=Free ShowAsConversation=Show as conversation list MessageListViewType=Show as table list +Token=Token diff --git a/htdocs/langs/en_US/users.lang b/htdocs/langs/en_US/users.lang index f9725646f3b..e1d92ed2c15 100644 --- a/htdocs/langs/en_US/users.lang +++ b/htdocs/langs/en_US/users.lang @@ -144,7 +144,7 @@ CloneCategoriesUser=Clone the user's categories ConfirmUserClone=Are you sure you want to clone the user: %s? NewEmailUserClone=Email address of the new user SocialNetworksUser=Social networks for user -ApiToken=API token +ApiTokens=API tokens ListOfTokensForUser=List of tokens for this user ListOfTokensForAllUsers=List of tokens for all users ListOfRightsForToken=List of rights for this token diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index f055cc85831..e6a8a3b497d 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -351,7 +351,7 @@ if (isset($reloadtoken)) { // If we add or del rights, we want to refresh the to if ($object->id > 0) { $person_name = !empty($object->firstname) ? $object->lastname.", ".$object->firstname : $object->lastname; - $title = $person_name." - ".$langs->trans('Card'); + $title = $person_name." - ".$langs->trans('ApiTokens'); } else { $title = $langs->trans("NewToken"); } @@ -392,7 +392,7 @@ if ($action == 'create') { print ''.$langs->trans('Entity').''.$mc->label.''; } - print ''.$langs->trans("ApiToken").''; + print ''.$langs->trans("Token").''; print ''; print ''; if (!empty($conf->use_javascript_ajax)) { @@ -460,7 +460,7 @@ if ($action == 'create') { print ''."\n"; // Token - print ''.$langs->trans("ApiToken").''; + print ''.$langs->trans("Token").''; print ''; print showValueWithClipboardCPButton($tokenvalue, 1, $tokenvalue); print ''; diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index 4fc0a736dce..6cac2feca5d 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -205,7 +205,7 @@ if (empty($reshook)) { */ $person_name = !empty($object->firstname) ? $object->lastname.", ".$object->firstname : $object->lastname; -$title = $person_name." - ".$langs->trans('Card'); +$title = $person_name." - ".$langs->trans('ApiTokens'); $help_url = ''; $nbtotalofrecords = ''; From fa3f46113a9e8e1912af7711fd387b2710fbf0e8 Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 7 Jul 2025 16:16:19 +0200 Subject: [PATCH 074/387] feat: change trans for understandability --- htdocs/langs/en_US/admin.lang | 2 +- htdocs/user/card.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index c63de1205c8..a46ffd65524 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -1963,7 +1963,7 @@ ApiProductionMode=Enable production mode (this will activate use of a cache for ApiExporerIs=You can explore and test the APIs at URL OnlyActiveElementsAreExposed=Only elements from enabled modules are exposed ApiKey=Key for API -UseApiKey=Use keys for API +UseRestApi=Use REST API WarningAPIExplorerDisabled=The API explorer has been disabled. API explorer is not required to provide API services. It is a tool for developer to find/test REST APIs. If you need this tool, go into setup of module API REST to activate it. ##### Bank ##### BankSetupModule=Bank module setup diff --git a/htdocs/user/card.php b/htdocs/user/card.php index b5fc6255bdd..1e78c2ac4ff 100644 --- a/htdocs/user/card.php +++ b/htdocs/user/card.php @@ -1237,7 +1237,7 @@ if ($action == 'create' || $action == 'adduserldap') { if (isModEnabled('api')) { // API key //$generated_password = getRandomPassword(false); - print ''.$langs->trans("UseApiKey").''; + print ''.$langs->trans("UseRestApi").''; print ''; print ''; print ''; @@ -2051,7 +2051,7 @@ if ($action == 'create' || $action == 'adduserldap') { // API key if (isModEnabled('api') && ($user->id == $id || $user->admin || $user->hasRight("api", "apikey", "generate"))) { - print ''.$langs->trans("UseApiKey").''; + print ''.$langs->trans("UseRestApi").''; print ''; print ''; print empty($object->api_key) ? $langs->trans("No") : $langs->trans("Yes"); @@ -2645,7 +2645,7 @@ if ($action == 'create' || $action == 'adduserldap') { // API key if (isModEnabled('api')) { - print ''.$langs->trans("UseApiKey").''; + print ''.$langs->trans("UseRestApi").''; print ''; if ($permissiontoeditpasswordandsee || $user->hasRight("api", "apikey", "generate")) { print 'api_key != '' ? ' checked="checked"' : "").'>'; From 9107c86c89feb848bd5029a9e1fdaf8c801c5849 Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 7 Jul 2025 16:28:39 +0200 Subject: [PATCH 075/387] feat: Not showing API operations if use api not defined for user --- htdocs/api/class/api_access.class.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/htdocs/api/class/api_access.class.php b/htdocs/api/class/api_access.class.php index 831d2785a21..e653baaf832 100644 --- a/htdocs/api/class/api_access.class.php +++ b/htdocs/api/class/api_access.class.php @@ -133,7 +133,7 @@ class DolibarrApiAccess implements iAuthenticate if ($api_key) { $userentity = 0; - $sql = "SELECT u.login, oat.token as api_key, oat.entity"; + $sql = "SELECT u.login, u.api_key as use_api, oat.token as api_key, oat.entity"; $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token AS oat"; $sql .= " JOIN ".MAIN_DB_PREFIX."user AS u ON u.rowid = oat.fk_user"; $sql .= " WHERE oat.token = '".$this->db->escape($api_key)."'"; @@ -148,6 +148,7 @@ class DolibarrApiAccess implements iAuthenticate $login = $obj->login; $stored_key = dolDecrypt($obj->api_key); $userentity = $obj->entity; + $use_api = $obj->use_api; if (!defined("DOLENTITY") && $conf->entity != ($obj->entity ? $obj->entity : 1)) { // If API was not forced with HTTP_DOLENTITY, and user is on another entity, so we reset entity to entity of user $conf->entity = ($obj->entity ? $obj->entity : 1); @@ -175,6 +176,12 @@ class DolibarrApiAccess implements iAuthenticate throw new RestException(401, $genericmessageerroruser); } + if (!$use_api) { + dol_syslog("functions_isallowed::check_user_api_key Authentication KO for api key: API not enabled for user", LOG_NOTICE); + sleep(1); // Anti brute force protection. Must be same delay when user and password are not valid. + throw new RestException(401, $genericmessageerroruser); + } + $fuser = new User($this->db); $result = $fuser->fetch(0, $login, '', 0, (empty($userentity) ? -1 : $conf->entity)); // If user is not entity 0, we search in working entity $conf->entity (that may have been forced to a different value than user entity) if ($result <= 0) { From 39a6cadf11d1c92cd5effddb3e41ddac1767bb4c Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 7 Jul 2025 17:04:39 +0200 Subject: [PATCH 076/387] feat: add picto for titles --- htdocs/api/admin/token_list.php | 2 +- htdocs/user/api_token/card.php | 2 +- htdocs/user/api_token/list.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php index 884aa05eb70..c649db72a64 100644 --- a/htdocs/api/admin/token_list.php +++ b/htdocs/api/admin/token_list.php @@ -311,7 +311,7 @@ print ''; print ''; print ''; -print_barre_liste($langs->trans("ListOfTokensForAllUsers"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, '', 0, $morehtmlright, '', $limit, 0, 0, 1); +print_barre_liste($langs->trans("ListOfTokensForAllUsers"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, 'fa-at', 0, $morehtmlright, '', $limit, 0, 0, 1); include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php'; diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index e6a8a3b497d..20b387278f2 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -501,7 +501,7 @@ if ($action == 'create') { print dol_get_fiche_end(); - print load_fiche_titre($langs->trans("ListOfRightsForToken"), '', ''); + print load_fiche_titre($langs->trans("ListOfRightsForToken"), '', 'fa-at'); print ''."\n"; diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index 6cac2feca5d..6d928975451 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -376,7 +376,7 @@ print ''; print ''; print ''; -print_barre_liste($langs->trans("ListOfTokensForUser"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, '', 0, $morehtmlright, '', $limit, 0, 0, 1); +print_barre_liste($langs->trans("ListOfTokensForUser"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, 'fa-at', 0, $morehtmlright, '', $limit, 0, 0, 1); include DOL_DOCUMENT_ROOT.'/core/tpl/massactions_pre.tpl.php'; From 256eea24eacb0bd1b9f0ba18252be5737b68efc2 Mon Sep 17 00:00:00 2001 From: yannis Date: Mon, 7 Jul 2025 17:05:27 +0200 Subject: [PATCH 077/387] feat: add nonce for js script --- htdocs/user/api_token/card.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/user/api_token/card.php b/htdocs/user/api_token/card.php index 20b387278f2..04c269accdf 100644 --- a/htdocs/user/api_token/card.php +++ b/htdocs/user/api_token/card.php @@ -957,7 +957,7 @@ if ($action == 'create') { print ''; print '
'; - print ''."\n"; @@ -864,7 +864,7 @@ function getStructuredData($type, $data = array()) "offers": { "@type": "Offer", "url": "https://example.com/anvil", - "priceCurrency": "'.dol_escape_json($data['currency'] ? $data['currency'] : $conf->currency).'", + "priceCurrency": "'.dol_escape_json($data['currency'] ? $data['currency'] : getDolCurrency()).'", "price": "'.dol_escape_json($data['price']).'", "itemCondition": "https://schema.org/UsedCondition", "availability": "https://schema.org/InStock", diff --git a/htdocs/core/modules/asset/doc/pdf_standard_asset.modules.php b/htdocs/core/modules/asset/doc/pdf_standard_asset.modules.php index 496b4e7b795..fb80e0fd496 100644 --- a/htdocs/core/modules/asset/doc/pdf_standard_asset.modules.php +++ b/htdocs/core/modules/asset/doc/pdf_standard_asset.modules.php @@ -756,7 +756,7 @@ class pdf_standard_asset extends ModelePDFAsset $hidetop = -1; } - $currency = !empty($currency) ? $currency : $conf->currency; + $currency = !empty($currency) ? $currency : getDolCurrency(); $default_font_size = pdf_getPDFFontSize($outputlangs); // Amount in (at tab_top - 1) diff --git a/htdocs/core/modules/commande/doc/pdf_einstein.modules.php b/htdocs/core/modules/commande/doc/pdf_einstein.modules.php index 58c551b49e0..7acda8d89bf 100644 --- a/htdocs/core/modules/commande/doc/pdf_einstein.modules.php +++ b/htdocs/core/modules/commande/doc/pdf_einstein.modules.php @@ -1256,7 +1256,7 @@ class pdf_einstein extends ModelePDFCommandes $hidetop = -1; } - $currency = !empty($currency) ? $currency : $conf->currency; + $currency = !empty($currency) ? $currency : getDolCurrency(); $default_font_size = pdf_getPDFFontSize($outputlangs); // Amount in (at tab_top - 1) diff --git a/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php b/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php index 9a72cd5c7e5..b32b39aa771 100644 --- a/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php +++ b/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php @@ -723,7 +723,7 @@ class pdf_eratosthene extends ModelePDFCommandes $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); } elseif ($object->lines[$i]->qty < 0 && isset($sub_options['subtotalshowtotalexludingvatonpdf'])) { - if (isModEnabled('multicurrency') && $object->multicurrency_code != $conf->currency) { + if (isModEnabled('multicurrency') && $object->multicurrency_code != getDolCurrency()) { $total_excl_tax = $object->getSubtotalLineMulticurrencyAmount($object->lines[$i]); } else { $total_excl_tax = $object->getSubtotalLineAmount($object->lines[$i]); @@ -1535,7 +1535,7 @@ class pdf_eratosthene extends ModelePDFCommandes $hidetop = -1; } - $currency = !empty($currency) ? $currency : $conf->currency; + $currency = !empty($currency) ? $currency : getDolCurrency(); $default_font_size = pdf_getPDFFontSize($outputlangs); // Amount in (at tab_top - 1) diff --git a/htdocs/core/modules/modMultiCurrency.class.php b/htdocs/core/modules/modMultiCurrency.class.php index 607cb67468e..d70ea600eb9 100644 --- a/htdocs/core/modules/modMultiCurrency.class.php +++ b/htdocs/core/modules/modMultiCurrency.class.php @@ -291,15 +291,15 @@ class modMultiCurrency extends DolibarrModules */ private function createFirstCurrency() { - global $conf, $user, $langs; + global $user, $langs; $multicurrency = new MultiCurrency($this->db); - if (! $multicurrency->checkCodeAlreadyExists($conf->currency)) { + if (! $multicurrency->checkCodeAlreadyExists(getDolCurrency())) { $langs->loadCacheCurrencies(''); - $multicurrency->code = $conf->currency; - $multicurrency->name = $langs->cache_currencies[$conf->currency]['label'].' ('.$langs->getCurrencySymbol($conf->currency).')'; + $multicurrency->code = getDolCurrency(); + $multicurrency->name = $langs->cache_currencies[getDolCurrency()]['label'].' ('.$langs->getCurrencySymbol(getDolCurrency()).')'; $r = $multicurrency->create($user); if ($r > 0) { diff --git a/htdocs/debugbar/class/DataCollector/DolibarrCollector.php b/htdocs/debugbar/class/DataCollector/DolibarrCollector.php index 1708a7ad724..f254be8258f 100644 --- a/htdocs/debugbar/class/DataCollector/DolibarrCollector.php +++ b/htdocs/debugbar/class/DataCollector/DolibarrCollector.php @@ -86,7 +86,7 @@ class DolibarrCollector extends DataCollector implements Renderable, AssetProvid $info = $langs->trans('Version').': '.DOL_VERSION.'
'; $info .= $langs->trans('Theme').': '.$conf->theme.'
'; $info .= $langs->trans('Locale').': ' . getDolGlobalString('MAIN_LANG_DEFAULT').'
'; - $info .= $langs->trans('Currency').': '.$conf->currency.'
'; + $info .= $langs->trans('Currency').': '.getDolCurrency().'
'; $info .= $langs->trans('Entity').': '.$conf->entity.'
'; $info .= $langs->trans('MaxSizeList').': '.($conf->liste_limit ?: getDolGlobalString('MAIN_SIZE_LISTE_LIMIT')).'
'; $info .= $langs->trans('MaxSizeForUploadedFiles').': ' . getDolGlobalString('MAIN_UPLOAD_DOC').'
'; diff --git a/htdocs/don/class/don.class.php b/htdocs/don/class/don.class.php index 5031f88399a..4b76c222659 100644 --- a/htdocs/don/class/don.class.php +++ b/htdocs/don/class/don.class.php @@ -1214,7 +1214,7 @@ class Don extends CommonObject $return .= '
'.$langs->trans("Company").' : '.$this->societe.''; } if (!empty($this->amount)) { - $return .= '
'.price($this->amount, 1, $langs, 1, -1, -1, $conf->currency).''; + $return .= '
'.price($this->amount, 1, $langs, 1, -1, -1, getDolCurrency()).''; } if (isset($this->status)) { $return .= '
'.$this->getLibStatut(3).'
'; diff --git a/htdocs/don/payment/card.php b/htdocs/don/payment/card.php index c99e000b330..5d747c11d92 100644 --- a/htdocs/don/payment/card.php +++ b/htdocs/don/payment/card.php @@ -140,7 +140,7 @@ print ''.$langs->trans('Mode').''.$langs->trans("PaymentType".$ print ''.$langs->trans('Numero').''.dol_escape_htmltag($object->num_payment).''; // Amount -print ''.$langs->trans('Amount').''.price($object->amount, 0, $outputlangs, 1, -1, -1, $conf->currency).''; +print ''.$langs->trans('Amount').''.price($object->amount, 0, $outputlangs, 1, -1, -1, getDolCurrency()).''; // Note public print ''.$langs->trans('Note').''.dol_string_onlythesehtmltags(dol_htmlcleanlastbr($object->note_public)).''; diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 11eaeb1fec2..0a8e32b2c36 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -2618,7 +2618,7 @@ class Expedition extends CommonObject $return .= ''; } $return .= '
'.$this->thirdparty->getNomUrl(1).'
'; - $return .= '
'.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency).' '.$langs->trans('HT').'
'; + $return .= '
'.price($this->total_ht, 0, $langs, 0, -1, -1, getDolCurrency()).' '.$langs->trans('HT').'
'; $return .= '
'.$this->getLibStatut(3).'
'; $return .= ''; $return .= ''; diff --git a/htdocs/main.inc.php b/htdocs/main.inc.php index 8e3420493d4..4ccb8fe0191 100644 --- a/htdocs/main.inc.php +++ b/htdocs/main.inc.php @@ -2404,7 +2404,7 @@ function top_menu_user($hideloginname = 0, $urllogout = '') $dropdownBody .= '
'.$langs->trans("VATIntraShort").': '.dol_print_profids(getDolGlobalString("MAIN_INFO_TVAINTRA"), 'VAT').''; $dropdownBody .= '
'.$langs->trans("Country").': '.($mysoc->country_code ? $langs->trans("Country".$mysoc->country_code) : '').''; if (isModEnabled('multicurrency')) { - $dropdownBody .= '
'.$langs->trans("Currency").': '.$conf->currency.''; + $dropdownBody .= '
'.$langs->trans("Currency").': '.getDolCurrency().''; } $dropdownBody .= ''; diff --git a/htdocs/margin/customerMargins.php b/htdocs/margin/customerMargins.php index c24407c16db..10cfcb27e4e 100644 --- a/htdocs/margin/customerMargins.php +++ b/htdocs/margin/customerMargins.php @@ -198,7 +198,7 @@ print ''; // Total Margin print ''; // Margin Rate diff --git a/htdocs/margin/productMargins.php b/htdocs/margin/productMargins.php index 77cce2d27b8..38aab2d3049 100644 --- a/htdocs/margin/productMargins.php +++ b/htdocs/margin/productMargins.php @@ -162,7 +162,7 @@ print '
'.$langs->trans("TotalMargin").''; -print ' '.$langs->getCurrencySymbol($conf->currency).''; // set by jquery (see below) +print ' '.$langs->getCurrencySymbol(getDolCurrency()).''; // set by jquery (see below) print '
'; // Total Margin print ''; // Margin Rate diff --git a/htdocs/margin/tabs/productMargins.php b/htdocs/margin/tabs/productMargins.php index 280ccc458ca..ec89b565df3 100644 --- a/htdocs/margin/tabs/productMargins.php +++ b/htdocs/margin/tabs/productMargins.php @@ -499,7 +499,7 @@ if ($id > 0 || !empty($ref)) { print ' \n"; } + + if ($nooutput) { + return $out; + } else { + print $out; + } } diff --git a/htdocs/core/lib/company.lib.php b/htdocs/core/lib/company.lib.php index cfe83b82b7e..0a4f007376e 100644 --- a/htdocs/core/lib/company.lib.php +++ b/htdocs/core/lib/company.lib.php @@ -1215,7 +1215,7 @@ function show_projects($conf, $langs, $db, $object, $backtopage = '', $nocreatel */ function show_contacts($conf, $langs, $db, $object, $backtopage = '', $showuserlogin = 0) { - global $user, $conf, $extrafields, $hookmanager; + global $user, $extrafields, $hookmanager; global $contextpage; require_once DOL_DOCUMENT_ROOT . '/core/class/html.formcompany.class.php'; @@ -1802,8 +1802,6 @@ function show_contacts($conf, $langs, $db, $object, $backtopage = '', $showuserl */ function show_actions_todo($conf, $langs, $db, $filterobj, $objcon = null, $noprint = 0, $actioncode = '') { - global $user, $conf; - $out = show_actions_done($conf, $langs, $db, $filterobj, $objcon, 1, $actioncode, 'todo'); if ($noprint) { @@ -1834,7 +1832,7 @@ function show_actions_todo($conf, $langs, $db, $filterobj, $objcon = null, $nopr */ function show_actions_done($conf, $langs, $db, $filterobj, $objcon = null, $noprint = 0, $actioncode = '', $donetodo = 'done', $filters = array(), $sortfield = 'a.datep,a.id', $sortorder = 'DESC', $module = '') { - global $user, $conf, $hookmanager; + global $hookmanager; global $form; global $param, $massactionbutton; @@ -1878,6 +1876,14 @@ function show_actions_done($conf, $langs, $db, $filterobj, $objcon = null, $nopr } $sortfield_new = implode(',', $sortfield_new_list); + $complete = (string) $filters['search_complete']; // Can be 'na', '0', '50', '100' + $percent = $complete !== '' ? $complete : -1; + if ((string) $complete == '0') { + $percent = '0'; + } elseif ((int) $complete == 100) { + $percent = '100'; + } + $sql = ''; if (isModEnabled('agenda')) { @@ -2273,7 +2279,7 @@ function show_actions_done($conf, $langs, $db, $filterobj, $objcon = null, $nopr $out .= '
'; $out .= '
'.$langs->trans("TotalMargin").''; -print ' '.$langs->getCurrencySymbol($conf->currency).''; // set by jquery (see below) +print ' '.$langs->getCurrencySymbol(getDolCurrency()).''; // set by jquery (see below) print '
'; - $out .= ''; + $out .= ''; // Action column if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { @@ -2303,9 +2309,9 @@ function show_actions_done($conf, $langs, $db, $filterobj, $objcon = null, $nopr $out .= ''; $out .= ''; $out .= ''; - // Status - $out .= ''; // Action column if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { @@ -2467,9 +2473,7 @@ function show_actions_done($conf, $langs, $db, $filterobj, $objcon = null, $nopr $out .= ''; // Title/Label of event @@ -2584,8 +2588,6 @@ function show_actions_done($conf, $langs, $db, $filterobj, $objcon = null, $nopr */ function show_subsidiaries($conf, $langs, $db, $object) { - global $user; - $i = -1; $sql = "SELECT s.rowid, s.client, s.fournisseur, s.nom as name, s.name_alias, s.email, s.address, s.zip, s.town, s.code_client, s.code_fournisseur, s.code_compta, s.code_compta_fournisseur, s.canvas, s.status"; @@ -2702,7 +2704,7 @@ function addEventTypeSQL(&$sql, $actioncode, $sqlANDOR = "AND") } /** - * Add Event Type SQL + * Add more SQL filters for event list * * @param string $sql $sql modified * @param string $donetodo donetodo @@ -2719,6 +2721,21 @@ function addOtherFilterSQL(&$sql, $donetodo, $now, $filters) } elseif ($donetodo == 'done') { $sql .= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '" . $db->idate($now) . "'))"; } + if (is_array($filters) && isset($filters['search_complete']) && $filters['search_complete'] === 'na') { + $sql .= " AND a.percent = -1"; + } + if (is_array($filters) && isset($filters['search_complete']) && $filters['search_complete'] === '0') { + $sql .= " AND a.percent = 0"; + } + if (is_array($filters) && isset($filters['search_complete']) && $filters['search_complete'] === '50') { + $sql .= " AND a.percent > 0 AND a.percent < 100"; + } + if (is_array($filters) && isset($filters['search_complete']) && $filters['search_complete'] === 'todo') { + $sql .= " AND a.percent >= 0 AND a.percent < 100"; + } + if (is_array($filters) && isset($filters['search_complete']) && $filters['search_complete'] === '100') { + $sql .= " AND a.percent = 100"; + } if (is_array($filters) && !empty($filters['search_agenda_label'])) { $sql .= natural_search('a.label', $filters['search_agenda_label']); } diff --git a/htdocs/product/agenda.php b/htdocs/product/agenda.php index f6f76dc608f..a6df0bac866 100644 --- a/htdocs/product/agenda.php +++ b/htdocs/product/agenda.php @@ -31,13 +31,6 @@ // Load Dolibarr environment require '../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php'; -require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; -require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -45,14 +38,20 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php'; +require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php'; // Load translation files required by the page $langs->load("companies"); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : str_replace('_', '', basename(dirname(__FILE__)).basename(__FILE__, '.php')); // To manage different context of search -if (GETPOST('actioncode', 'array')) { - $actioncode = GETPOST('actioncode', 'array', 3); +if (GETPOSTISARRAY('actioncode')) { + $actioncode = GETPOST('actioncode', 'array:alpha', 3); if (!count($actioncode)) { $actioncode = '0'; } @@ -61,6 +60,9 @@ if (GETPOST('actioncode', 'array')) { } $search_rowid = GETPOST('search_rowid'); $search_agenda_label = GETPOST('search_agenda_label'); +$search_complete = GETPOST('search_complete'); +$search_dateevent_start = GETPOSTDATE('dateevent_start'); +$search_dateevent_end = GETPOSTDATE('dateevent_end'); $id = GETPOSTINT('id'); $ref = GETPOST('ref', 'alpha'); @@ -69,9 +71,10 @@ $limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit; $sortfield = GETPOST('sortfield', 'aZ09comma'); $sortorder = GETPOST('sortorder', 'aZ09comma'); $page = GETPOSTISSET('pageplusone') ? (GETPOSTINT('pageplusone') - 1) : GETPOSTINT("page"); -if (empty($page) || $page == -1) { +if (empty($page) || $page < 0 || GETPOST('button_search', 'alpha') || GETPOST('button_removefilter', 'alpha')) { + // If $page is not defined, or '' or -1 or if we click on clear filters $page = 0; -} // If $page is not defined, or '' or -1 +} $offset = $limit * $page; $pageprev = $page - 1; $pagenext = $page + 1; @@ -128,7 +131,9 @@ if (empty($reshook)) { // Purge search criteria if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers $actioncode = ''; + $search_rowid = ''; $search_agenda_label = ''; + $search_complete = ''; } } @@ -188,6 +193,8 @@ dol_print_object_info($object, 1); print ''; +print '
'; + print dol_get_fiche_end(); @@ -224,10 +231,32 @@ if (isModEnabled('agenda') && ($user->hasRight('agenda', 'myactions', 'read') || $param = '&id='.$id; if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) { - $param .= '&contextpage='.$contextpage; + $param .= '&contextpage='.urlencode($contextpage); } if ($limit > 0 && $limit != $conf->liste_limit) { - $param .= '&limit='.$limit; + $param .= '&limit='.((int) $limit); + } + if ($search_rowid) { + $param .= '&search_rowid='.urlencode($search_rowid); + } + if ($actioncode !== '' && $actioncode !== '-1') { + $param .= '&actioncode='.urlencode($actioncode); + } + if ($search_agenda_label) { + $param .= '&search_agenda_label='.urlencode($search_agenda_label); + } + if ($search_complete != '') { + $param .= '&search_complete='.urlencode($search_complete); + } + if ($search_dateevent_start != '') { + $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); + $param .= '&dateevent_startmonth='.GETPOSTINT('dateevent_startmonth'); + $param .= '&dateevent_startday='.GETPOSTINT('dateevent_startday'); + } + if ($search_dateevent_end != '') { + $param .= '&dateevent_endyear='.GETPOSTINT('dateevent_endyear'); + $param .= '&dateevent_endmonth='.GETPOSTINT('dateevent_endmonth'); + $param .= '&dateevent_endday='.GETPOSTINT('dateevent_endday'); } // Try to know count of actioncomm from cache @@ -240,15 +269,16 @@ if (isModEnabled('agenda') && ($user->hasRight('agenda', 'myactions', 'read') || $titlelist = $langs->trans("Actions").(is_numeric($nbEvent) ? '('.$nbEvent.')' : ''); } - print_barre_liste($titlelist, 0, $_SERVER["PHP_SELF"], '', $sortfield, $sortorder, '', 0, -1, '', 0, $morehtmlright, '', 0, 1, 0); + print_barre_liste($titlelist, 0, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', 0, -1, '', 0, $morehtmlright, '', 0, 1, 0); // List of all actions $filters = array(); $filters['search_agenda_label'] = $search_agenda_label; $filters['search_rowid'] = $search_rowid; + $filters['search_complete'] = $search_complete; // Can be 'na', '0', '100', '50' // TODO Replace this with same code than into list.php - show_actions_done($conf, $langs, $db, $object, null, 0, $actioncode, '', $filters, $sortfield, $sortorder); + show_actions_done($conf, $langs, $db, $object, null, 0, $actioncode, '', $filters, $sortfield, $sortorder, $object->module); } diff --git a/htdocs/projet/agenda.php b/htdocs/projet/agenda.php index 049008a455b..9b9d53ef6df 100644 --- a/htdocs/projet/agenda.php +++ b/htdocs/projet/agenda.php @@ -63,16 +63,20 @@ $offset = $limit * $page; $pageprev = $page - 1; $pagenext = $page + 1; -if (GETPOST('actioncode', 'array')) { - $actioncode = GETPOST('actioncode', 'array', 3); +if (GETPOSTISARRAY('actioncode')) { + $actioncode = GETPOST('actioncode', 'array:alpha', 3); if (!count($actioncode)) { $actioncode = '0'; } } else { $actioncode = GETPOST("actioncode", "alpha", 3) ? GETPOST("actioncode", "alpha", 3) : (GETPOST("actioncode") == '0' ? '0' : getDolGlobalString('AGENDA_DEFAULT_FILTER_TYPE_FOR_OBJECT')); } + $search_rowid = GETPOST('search_rowid'); $search_agenda_label = GETPOST('search_agenda_label'); +$search_complete = GETPOST('search_complete'); +$search_dateevent_start = GETPOSTDATE('dateevent_start'); +$search_dateevent_end = GETPOSTDATE('dateevent_end'); $hookmanager->initHooks(array('projectcardinfo')); @@ -100,9 +104,11 @@ if ($reshook < 0) { } // Purge search criteria -if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All test are required to be compatible with all browsers +if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers $actioncode = ''; + $search_rowid = ''; $search_agenda_label = ''; + $search_complete = ''; } @@ -112,7 +118,6 @@ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x' */ $form = new Form($db); -$object = new Project($db); if ($id > 0 || !empty($ref)) { $object->fetch($id, $ref); @@ -225,6 +230,29 @@ if (!empty($object->id)) { if ($limit > 0 && $limit != $conf->liste_limit) { $param .= '&limit='.((int) $limit); } + if ($search_rowid) { + $param .= '&search_rowid='.urlencode($search_rowid); + } + if ($actioncode !== '' && $actioncode !== '-1') { + $param .= '&actioncode='.urlencode($actioncode); + } + if ($search_agenda_label) { + $param .= '&search_agenda_label='.urlencode($search_agenda_label); + } + if ($search_complete != '') { + $param .= '&search_complete='.urlencode($search_complete); + } + if ($search_dateevent_start != '') { + $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); + $param .= '&dateevent_startmonth='.GETPOSTINT('dateevent_startmonth'); + $param .= '&dateevent_startday='.GETPOSTINT('dateevent_startday'); + } + if ($search_dateevent_end != '') { + $param .= '&dateevent_endyear='.GETPOSTINT('dateevent_endyear'); + $param .= '&dateevent_endmonth='.GETPOSTINT('dateevent_endmonth'); + $param .= '&dateevent_endday='.GETPOSTINT('dateevent_endday'); + } + require_once DOL_DOCUMENT_ROOT.'/core/lib/memory.lib.php'; $cachekey = 'count_events_project_'.$object->id; @@ -235,13 +263,15 @@ if (!empty($object->id)) { $titlelist = $langs->trans("Actions").(is_numeric($nbEvent) ? '('.$nbEvent.')' : ''); } - print_barre_liste($titlelist, 0, $_SERVER["PHP_SELF"], '', $sortfield, $sortorder, '', 0, -1, '', 0, $morehtmlright, '', 0, 1, 0); + print_barre_liste($titlelist, 0, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', 0, -1, '', 0, $morehtmlright, '', 0, 1, 0); // List of all actions $filters = array(); $filters['search_agenda_label'] = $search_agenda_label; $filters['search_rowid'] = $search_rowid; + $filters['search_complete'] = $search_complete; // Can be 'na', '0', '100', '50' + // TODO Replace this with same code than into list.php show_actions_done($conf, $langs, $db, $object, null, 0, $actioncode, '', $filters, $sortfield, $sortorder); } diff --git a/htdocs/societe/agenda.php b/htdocs/societe/agenda.php index 407542626b4..40c58463304 100644 --- a/htdocs/societe/agenda.php +++ b/htdocs/societe/agenda.php @@ -30,12 +30,6 @@ // Load Dolibarr environment require '../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php'; -require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -43,14 +37,20 @@ require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php'; +require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; // Load translation files required by the page $langs->loadLangs(array('agenda', 'bills', 'companies', 'orders', 'propal')); +$action = GETPOST('action', 'aZ09'); $contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'thirdpartyagenda'; -if (GETPOST('actioncode', 'array')) { - $actioncode = GETPOST('actioncode', 'array', 3); +if (GETPOSTISARRAY('actioncode')) { + $actioncode = GETPOST('actioncode', 'array:alpha', 3); if (!count($actioncode)) { $actioncode = '0'; } @@ -60,14 +60,18 @@ if (GETPOST('actioncode', 'array')) { $search_rowid = GETPOST('search_rowid'); $search_agenda_label = GETPOST('search_agenda_label'); +$search_complete = GETPOST('search_complete'); +$search_dateevent_start = GETPOSTDATE('dateevent_start'); +$search_dateevent_end = GETPOSTDATE('dateevent_end'); $limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit; $sortfield = GETPOST('sortfield', 'aZ09comma'); $sortorder = GETPOST('sortorder', 'aZ09comma'); -$page = GETPOSTISSET('pageplusone') ? (GETPOSTINT('pageplusone') - 1) : GETPOSTINT("page"); -if (empty($page) || $page == -1) { +$page = GETPOSTISSET('pageplusone') ? (GETPOSTINT('pageplusone') - 1) : GETPOSTINT('page'); +if (empty($page) || $page < 0 || GETPOST('button_search', 'alpha') || GETPOST('button_removefilter', 'alpha')) { + // If $page is not defined, or '' or -1 or if we click on clear filters $page = 0; -} // If $page is not defined, or '' or -1 +} $offset = $limit * $page; $pageprev = $page - 1; $pagenext = $page + 1; @@ -78,6 +82,15 @@ if (!$sortorder) { $sortorder = 'DESC,DESC'; } +if (GETPOST('actioncode', 'array')) { + $actioncode = GETPOST('actioncode', 'array', 3); + if (!count($actioncode)) { + $actioncode = '0'; + } +} else { + $actioncode = GETPOST("actioncode", "alpha", 3) ? GETPOST("actioncode", "alpha", 3) : (GETPOST("actioncode") == '0' ? '0' : getDolGlobalString('AGENDA_DEFAULT_FILTER_TYPE_FOR_OBJECT')); +} + // Initialize a technical objects $object = new Societe($db); @@ -119,7 +132,9 @@ if (empty($reshook)) { // Purge search criteria if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) { // All tests are required to be compatible with all browsers $actioncode = ''; + $search_rowid = ''; $search_agenda_label = ''; + $search_complete = ''; } } @@ -158,6 +173,8 @@ dol_print_object_info($object, 1); print ''; +print '
'; + print dol_get_fiche_end(); @@ -210,6 +227,28 @@ if (isModEnabled('agenda') && ($user->hasRight('agenda', 'myactions', 'read') || if ($limit > 0 && $limit != $conf->liste_limit) { $param .= '&limit='.((int) $limit); } + if ($search_rowid) { + $param .= '&search_rowid='.urlencode($search_rowid); + } + if ($actioncode !== '' && $actioncode !== '-1') { + $param .= '&actioncode='.urlencode($actioncode); + } + if ($search_agenda_label) { + $param .= '&search_agenda_label='.urlencode($search_agenda_label); + } + if ($search_complete != '') { + $param .= '&search_complete='.urlencode($search_complete); + } + if ($search_dateevent_start != '') { + $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); + $param .= '&dateevent_startmonth='.GETPOSTINT('dateevent_startmonth'); + $param .= '&dateevent_startday='.GETPOSTINT('dateevent_startday'); + } + if ($search_dateevent_end != '') { + $param .= '&dateevent_endyear='.GETPOSTINT('dateevent_endyear'); + $param .= '&dateevent_endmonth='.GETPOSTINT('dateevent_endmonth'); + $param .= '&dateevent_endday='.GETPOSTINT('dateevent_endday'); + } // Try to know count of actioncomm from cache require_once DOL_DOCUMENT_ROOT.'/core/lib/memory.lib.php'; @@ -221,12 +260,13 @@ if (isModEnabled('agenda') && ($user->hasRight('agenda', 'myactions', 'read') || $titlelist = $langs->trans("Actions").(is_numeric($nbEvent) ? '('.$nbEvent.')' : ''); } - print_barre_liste($titlelist, 0, $_SERVER["PHP_SELF"], '', $sortfield, $sortorder, '', 0, -1, '', 0, $morehtmlright, '', 0, 1, 0); + print_barre_liste($titlelist, 0, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', 0, -1, '', 0, $morehtmlright, '', 0, 1, 0); // List of all actions $filters = array(); $filters['search_agenda_label'] = $search_agenda_label; $filters['search_rowid'] = $search_rowid; + $filters['search_complete'] = $search_complete; // Can be 'na', '0', '100', '50' // TODO Replace this with same code than into list.php show_actions_done($conf, $langs, $db, $object, null, 0, $actioncode, '', $filters, $sortfield, $sortorder, $object->module); From 312ea7d9ebcdc4b5c9146018ea38f097f2e31fee Mon Sep 17 00:00:00 2001 From: John BOTELLA <68917336+thersane-john@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:03:49 +0100 Subject: [PATCH 142/387] fix css login page patch 01 (#36313) Co-authored-by: Laurent Destailleur --- htdocs/public/webportal/css/login.css | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/htdocs/public/webportal/css/login.css b/htdocs/public/webportal/css/login.css index 492ae7cf292..2c862812779 100644 --- a/htdocs/public/webportal/css/login.css +++ b/htdocs/public/webportal/css/login.css @@ -126,16 +126,30 @@ body.login-page { color: var(--login-form-icon-color); } -input.login__input { +.login__field:has(.login__icon + select.login__input) .login__icon { + top: 33px; +} + + +:is(input,select).login__input { --border-radius: 0; + --input-width: 75%; border: none; border-bottom: 2px solid var(--login-form-border-color); - background: none; + background: #fff; padding: 10px 10px 10px 36px; - width: 75%; + width: var(--input-width); transition: .2s; } +input.login__input[name="security_code"] { + width: calc(var(--input-width) - 110px); +} + +select.login__input { + padding: 10px 10px 5px 36px; +} + .login__input:active, .login__input:focus, .login__input:hover { @@ -222,8 +236,8 @@ input.login__input { background-position: center; } - :where(.login-form-right) input.login__input { - width: 100%; + :where(.login-form-right) :is(input,select).login__input { + --input-width: 100%; } :where(.login-form-right) .login-screen { From bbbb958dc04e40e4f508823e6a876cba1a494385 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 18 Nov 2025 22:03:57 +0100 Subject: [PATCH 143/387] Fix CI --- htdocs/blockedlog/admin/blockedlog_archives.php | 6 +++--- htdocs/langs/en_US/blockedlog.lang | 1 + htdocs/langs/en_US/main.lang | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/htdocs/blockedlog/admin/blockedlog_archives.php b/htdocs/blockedlog/admin/blockedlog_archives.php index 97ccb18ba82..66d6e736b95 100644 --- a/htdocs/blockedlog/admin/blockedlog_archives.php +++ b/htdocs/blockedlog/admin/blockedlog_archives.php @@ -273,14 +273,14 @@ if (GETPOST('action') == 'upload' && $user->hasRight('blockedlog', 'read')) { / // Print line with title fwrite($fh, "BEGIN - date=".$yearmonthdateofexport .';'.$langs->transnoentities('Id') - .';'.$langs->transnoentities('DateCeation') + .';'.$langs->transnoentities('DateCreation') .';'.$langs->transnoentities('Action') .';'.$langs->transnoentities('Amounts') .';'.$langs->transnoentities('Ref') .';'.$langs->transnoentities('Date') .';'.$langs->transnoentities('User') - .';'.$langs->transnoentities('LinkToRef') - .';'.$langs->transnoentities('LinkToType') + .';'.$langs->transnoentities('LinkTo') + .';'.$langs->transnoentities('LinkType') .';'.$langs->transnoentities('FullData') .';'.$langs->transnoentities('Version') .';'.$langs->transnoentities('Fingerprint') diff --git a/htdocs/langs/en_US/blockedlog.lang b/htdocs/langs/en_US/blockedlog.lang index 6d9c38a3768..f2ba1d3788c 100644 --- a/htdocs/langs/en_US/blockedlog.lang +++ b/htdocs/langs/en_US/blockedlog.lang @@ -23,6 +23,7 @@ BlockedLogBillPreview=Customer invoice preview BlockedlogInfoDialog=Log Details ListOfTrackedEvents=List of tracked events Fingerprint=Fingerprint +FingerprintExport=Fingerprint export DownloadLogCSV=Export unalterable logs (CSV) DataOfArchivedEvent=Complete data of archived event DataOfArchivedEventHelp=This field contains the complementary data that was archived on real time. Even if some parent business event could have been canceled or modified, the data stored here is the original data, and it can't be modified. diff --git a/htdocs/langs/en_US/main.lang b/htdocs/langs/en_US/main.lang index 1130fd18023..fc31d632526 100644 --- a/htdocs/langs/en_US/main.lang +++ b/htdocs/langs/en_US/main.lang @@ -848,6 +848,7 @@ AttributeCode=Attribute code URLPhoto=URL of photo/logo SetLinkToAnotherThirdParty=Link to another third party LinkTo=Link to +LinkType=Link type LinkToProposal=Link to proposal LinkToExpedition= Link to expedition LinkToOrder=Link to order From d9ec700a3e1629d3af03566383a3671ec290c117 Mon Sep 17 00:00:00 2001 From: Marc <99648320+emheyarssi@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:06:40 +0100 Subject: [PATCH 144/387] FIX #36306 (#36307) --- htdocs/categories/class/categorie.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/categories/class/categorie.class.php b/htdocs/categories/class/categorie.class.php index e13c2870e90..b84bacaaed0 100644 --- a/htdocs/categories/class/categorie.class.php +++ b/htdocs/categories/class/categorie.class.php @@ -1050,7 +1050,7 @@ class Categorie extends CommonObject $tmpobj->fetch($rec['fk_object']); // The fetch will erase $tmpobj->id only if it succeed. // @phpstan-ignore-next-line if ($tmpobj->id > 0) { // Failing fetch may happen for example when a category supplier was set and third party was moved as customer only. The object supplier can't be loaded. - $objs[] = $tmpobj; + $objs[] = clone $tmpobj; } } } From 947389b07240269abed8fb60a42b0b6c6bf4d843 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 18 Nov 2025 22:45:56 +0100 Subject: [PATCH 145/387] NEW Disable by default obfuscation methods and function in extrafields evaluable strings. Can re-enable with MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL=1 --- htdocs/conf/conf.php.example | 10 ++++++++++ htdocs/core/lib/functions.lib.php | 7 ++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/htdocs/conf/conf.php.example b/htdocs/conf/conf.php.example index 294aac01619..e2e265a5379 100644 --- a/htdocs/conf/conf.php.example +++ b/htdocs/conf/conf.php.example @@ -312,6 +312,16 @@ $dolibarr_main_prod='1'; // $dolibarr_main_restrict_os_commands='mariadb-dump, mariadb, mysqldump, mysql, pg_dump, pg_restore, clamdscan, clamdscan.exe'; +// dolibarr_main_restrict_eval_methods +// ================================== +// To restrict commands you can execute in custom calculated fields like "computed fields" of extrafields or string +// conditions of extrafields. +// Default value: 'getDolGlobalString,getDolGlobalInt,fetchNoCompute' +// Examples: +// $dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,fetchNoCompute'; +// +//$dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,fetchNoCompute'; + // dolibarr_main_disabled_modules // ================================== // To restrict the activation and use of certain potentially security-sensitive modules. diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 16d76e8c67c..6c3f828e76d 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -12023,7 +12023,7 @@ function dol_eval_standard($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestr $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("array_all", "array_any", "array_diff_ukey", "array_filter", "array_find", "array_find_key", "array_map", "array_reduce", "array_intersect_uassoc", "array_intersect_ukey", "array_walk", "array_walk_recursive")); $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("usort", "uasort", "uksort", "preg_replace_callback", "preg_replace_callback_array", "header_register_callback")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("set_error_handler", "set_exception_handler", "libxml_set_external_entity_loader", "register_shutdown_function", "register_tick_function", "unregister_tick_function")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("error_log", "set_error_handler", "set_exception_handler", "libxml_set_external_entity_loader", "register_shutdown_function", "register_tick_function", "unregister_tick_function")); $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("spl_autoload_register", "spl_autoload_unregister", "iterator_apply", "session_set_save_handler")); $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("forward_static_call", "forward_static_call_array", "register_postsend_function")); @@ -12037,7 +12037,7 @@ function dol_eval_standard($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestr $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_compress_dir", "dol_decode", "dol_dir_list", "dol_dir_list_in_database", "dol_delete_file", "dol_delete_dir", "dol_delete_dir_recursive", "dol_copy", "archiveOrBackupFile")); // more dolibarr functions $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("chdir", "dir", "fopen", "file", "file_exists", "file_get_contents", "file_put_contents", "fget", "fgetc", "fgetcsv", "fputs", "fputscsv", "fpassthru", "fscanf", "fseek", "fwrite", "is_file", "is_dir", "is_link", "mkdir", "opendir", "rmdir", "scandir", "symlink", "touch", "unlink", "umask")); $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include")); - if (getDolGlobalString('MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL')) { // We disabllow all function that allow to obfuscate the real name of a function + if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { // We disallow all function that allow to obfuscate the real name of a function // @phpcs:ignore $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("base64" . "_" . "decode", "rawurl" . "decode", "url" . "decode", "str" . "_rot13", "hex" . "2bin")); // name of forbidden functions are split to avoid false positive $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concatdesc")); // native dolibarr functions @@ -12072,7 +12072,8 @@ function dol_eval_standard($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestr // Now accept only white-listed allowed function and classes - if (getDolGlobalString("MAIN_ALLOW_ONLY_WHITELIST_CLASS_AND_FUNCTION_IN_DOL_EVAL")) { + global $dolibarr_main_restrict_eval_methods; + if (!empty($dolibarr_main_restrict_os_commands)) { // TODO Get all pattern '/(\w+)\(/', then check that $reg[1] is a defined class or a function into a given list } From af0fcb0c2437401d6ac0cd22c45130dec2c93a6b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 18 Nov 2025 22:51:24 +0100 Subject: [PATCH 146/387] Doc --- htdocs/conf/conf.php.example | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/htdocs/conf/conf.php.example b/htdocs/conf/conf.php.example index e2e265a5379..e2e5fe2498c 100644 --- a/htdocs/conf/conf.php.example +++ b/htdocs/conf/conf.php.example @@ -314,13 +314,13 @@ $dolibarr_main_restrict_os_commands='mariadb-dump, mariadb, mysqldump, mysql, pg // dolibarr_main_restrict_eval_methods // ================================== -// To restrict commands you can execute in custom calculated fields like "computed fields" of extrafields or string -// conditions of extrafields. -// Default value: 'getDolGlobalString,getDolGlobalInt,fetchNoCompute' +// A whitelist of functions and methods to restrict the commands you can execute in a custom calculated fields, like "computed fields" of +// extrafields or string conditions of extrafields. +// Default value: 'getDolGlobalString,getDolGlobalInt,fetchNoCompute,hasRight,isModEnabled,abs,round' // Examples: -// $dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,fetchNoCompute'; +// $dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,fetchNoCompute,hasRight,isModEnabled,abs,round'; // -//$dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,fetchNoCompute'; +//$dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,fetchNoCompute,hasRight,isModEnabled,abs,round'; // dolibarr_main_disabled_modules // ================================== From 9818c76f7f6d9e140e4b319cc630f35f9abf7a3b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 18 Nov 2025 23:34:23 +0100 Subject: [PATCH 147/387] MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL replaced with MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL --- ChangeLog | 2 +- htdocs/admin/system/security.php | 4 +-- htdocs/conf/conf.php.example | 4 +-- htdocs/core/lib/functions.lib.php | 43 ++++++++++++++++++++++++++----- test/phpunit/SecurityTest.php | 18 ++++++------- 5 files changed, 50 insertions(+), 21 deletions(-) diff --git a/ChangeLog b/ChangeLog index 693af740add..1a0593b3f39 100644 --- a/ChangeLog +++ b/ChangeLog @@ -35,7 +35,7 @@ The following changes may create regressions for some external modules, but were * Property ->picto of module descriptors must contains the image extension if it is not a font awesome tag. Example: $this->picto="mymoduleimg.png"; * Stock movement API GET method output variable names has been harmonized with POST input parameter names * $conf is no more allowed into computed formulae. You ca replace use of $conf->currency by NEW Introduce getCurrency() and $conf->global->xxx by getDolGlobalString('xxx') - +* Concatenation into computed property of extrafields is off by default. You can enable it with ***** ChangeLog for 22.0.3 compared to 22.0.2 ***** diff --git a/htdocs/admin/system/security.php b/htdocs/admin/system/security.php index 3043afd5c63..3d1b5995ed7 100644 --- a/htdocs/admin/system/security.php +++ b/htdocs/admin/system/security.php @@ -972,8 +972,8 @@ print '
'; print 'MAIN_ALLOW_SVG_FILES_AS_EXTERNAL_LINKS = '.getDolGlobalString('MAIN_ALLOW_SVG_FILES_AS_EXTERNAL_LINKS', ''.$langs->trans("Undefined").'   ('.$langs->trans("Recommended").': '.$langs->trans("Undefined").' '.$langs->trans("or").' 0)')."
"; print '
'; -print 'MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL = '.(getDolGlobalString('MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL') ? '1' : ''.$langs->trans("Undefined").''); -print '   ('.$langs->trans("Recommended").": 1 - may break use of concatenation function like . or dol_concatdesc into extra fields conditions or formula)
"; +print 'MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL = '.getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL', ''.$langs->trans("Undefined").''); +print '   ('.$langs->trans("Recommended").": 0 - The value 1 allows the use of concatenation functions like . or dol_concat into extra fields conditions or formula but is not secured)
"; print '
'; diff --git a/htdocs/conf/conf.php.example b/htdocs/conf/conf.php.example index e2e5fe2498c..6cfcee65416 100644 --- a/htdocs/conf/conf.php.example +++ b/htdocs/conf/conf.php.example @@ -318,9 +318,9 @@ $dolibarr_main_restrict_os_commands='mariadb-dump, mariadb, mysqldump, mysql, pg // extrafields or string conditions of extrafields. // Default value: 'getDolGlobalString,getDolGlobalInt,fetchNoCompute,hasRight,isModEnabled,abs,round' // Examples: -// $dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,fetchNoCompute,hasRight,isModEnabled,abs,round'; +// $dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,fetchNoCompute,hasRight,isModEnabled,abs,round,dol_concat'; // -//$dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,fetchNoCompute,hasRight,isModEnabled,abs,round'; +$dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,fetchNoCompute,hasRight,isModEnabled,abs,round'; // dolibarr_main_disabled_modules // ================================== diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 6c3f828e76d..a4d8817c8c3 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -9837,7 +9837,18 @@ function dol_concatdesc($text1, $text2, $forxml = false, $invert = false) return $ret; } - +/** + * Concat 2 strings. Can be used for dol_eval strings for example. + * + * @param string $text1 Text 1 + * @param string $text2 Text 2 + * @return string Text 1 + new line + Text2 + * @see dol_textishtml() + */ +function dol_concat($text1, $text2) +{ + return $text1.$text2; +} /** * Return array of possible common substitutions. This includes several families like: 'system', 'mycompany', 'object', 'objectamount', 'date', 'user' @@ -11883,6 +11894,13 @@ function dol_eval_standard($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestr } try { + // Set $dolibarr_main_restrict_eval_methods_array + global $dolibarr_main_restrict_eval_methods; + $dolibarr_main_restrict_eval_methods_array = array(); + if (!empty($dolibarr_main_restrict_eval_methods)) { + $dolibarr_main_restrict_eval_methods_array = explode(',', $dolibarr_main_restrict_eval_methods); + } + // Test on dangerous char (used for RCE), we allow only characters to make PHP variable testing if ($onlysimplestring == '1' || $onlysimplestring == '2') { // We must accept with 1: '1 && getDolGlobalInt("doesnotexist1") && getDolGlobalString("MAIN_FEATURES_LEVEL")' @@ -11998,7 +12016,7 @@ function dol_eval_standard($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestr } // Disallow also concat - if (getDolGlobalString('MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL')) { + if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { if (preg_match('/[^0-9]+\.[^0-9]+/', $s)) { // We refuse . if not between 2 numbers if ($returnvalue) { return 'Bad string syntax to evaluate (dot char is forbidden): ' . $s; @@ -12031,7 +12049,7 @@ function dol_eval_standard($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestr $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once")); $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "dol_eval_new", "dol_eval_standard", "dol_concatdesc", "executeCLI", "verifCond", "GETPOST", "dolEncrypt", "dolDecrypt")); // native dolibarr functions + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "dol_eval_new", "dol_eval_standard", "executeCLI", "verifCond", "GETPOST", "dolEncrypt", "dolDecrypt")); // native dolibarr functions $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("readline_completion_function", "readline_callback_handler_install")); $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_compress_dir", "dol_decode", "dol_dir_list", "dol_dir_list_in_database", "dol_delete_file", "dol_delete_dir", "dol_delete_dir_recursive", "dol_copy", "archiveOrBackupFile")); // more dolibarr functions @@ -12040,10 +12058,22 @@ function dol_eval_standard($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestr if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { // We disallow all function that allow to obfuscate the real name of a function // @phpcs:ignore $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("base64" . "_" . "decode", "rawurl" . "decode", "url" . "decode", "str" . "_rot13", "hex" . "2bin")); // name of forbidden functions are split to avoid false positive - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concatdesc")); // native dolibarr functions + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concat", "dol_concatdesc")); // native dolibarr functions + } + // Remove from blacklist the function that are into the whitelist + foreach ($forbiddenphpfunctions as $key => $forbiddenphpfunction) { + if (in_array($forbiddenphpfunction, $dolibarr_main_restrict_eval_methods_array)) { + unset($forbiddenphpfunctions[$key]); + } } $forbiddenphpmethods = array('invoke', 'invokeArgs'); // Method of ReflectionFunction to execute a function + // Remove from blacklist the function that are into the whitelist + foreach ($forbiddenphpmethods as $key => $forbiddenphpmethod) { + if (in_array($forbiddenphpmethod, $dolibarr_main_restrict_eval_methods_array)) { + unset($forbiddenphpmethods[$key]); + } + } $forbiddenphpregex = 'global\s*\$'; $forbiddenphpregex .= '|'; @@ -12071,9 +12101,8 @@ function dol_eval_standard($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestr } - // Now accept only white-listed allowed function and classes - global $dolibarr_main_restrict_eval_methods; - if (!empty($dolibarr_main_restrict_os_commands)) { + // Accept only white-listed allowed function and classes + if (!empty($dolibarr_main_restrict_eval_methods)) { // TODO Get all pattern '/(\w+)\(/', then check that $reg[1] is a defined class or a function into a given list } diff --git a/test/phpunit/SecurityTest.php b/test/phpunit/SecurityTest.php index 0771c3dddc8..51a022efc34 100644 --- a/test/phpunit/SecurityTest.php +++ b/test/phpunit/SecurityTest.php @@ -615,7 +615,7 @@ class SecurityTest extends CommonClassTest $conf->global->MAIN_USE_DOL_EVAL_NEW = 0; //$conf->global->MAIN_USE_DOL_EVAL_NEW = 1; $conf->global->MAIN_ALLOW_DOUBLE_COLON_IN_DOL_EVAL = 0; - $conf->global->MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL = 0; + $conf->global->MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL = 1; //$resulttest = dol_eval('((getDolGlobalString("MAIN_USE_ADVANCED_PERMS") ? $user->hasRight("user","group_advance","read") : $user->hasRight("user","user","lire")) || $user->admin) && !(isModEnabled("multicompany") && $conf->entity > 1 && getDolGlobalString("MULTICOMPANY_TRANSVERSE_MODE"))', 1, 0); @@ -734,7 +734,7 @@ class SecurityTest extends CommonClassTest print "result8b = ".$result."\n"; $this->assertStringContainsString('Bad string syntax to evaluate (mode 1, found call of a function or method without using the direct name of the function)', $result, 'The string was not detected as evil'); - $conf->global->MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL = 1; + $conf->global->MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL = 0; $result = (string) dol_eval('$a="test"; $$a;', 1, 0); print "result9 = ".$result."\n"; @@ -803,24 +803,24 @@ class SecurityTest extends CommonClassTest $this->assertEquals('1', $result, 'The string was not detected as evil'); - // Test option MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL + // Test option MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL - $conf->global->MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL = 0; + $conf->global->MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL = 1; $mainmenu = 'ex'; $result = (string) dol_eval('$mainmenu.\'ec\'', 1, 0); print "resultconcat1 = ".$result."\n"; - $this->assertStringContainsString('exec', $result, 'With MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL off. we should accept concat'); + $this->assertStringContainsString('exec', $result, 'With MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL on. we should accept concat'); $mainmenu = 'ex'; $leftmenu = 'ec'; $result = (string) dol_eval("\$mainmenu.\$leftmenu", 1, 0); print "resultconcat2 = ".$result."\n"; - $this->assertStringContainsString('exec', $result, 'With MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL off. we should accept concat'); + $this->assertStringContainsString('exec', $result, 'With MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL on. we should accept concat'); - // Test option MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL = 1 + // Test option MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL = 0 - $conf->global->MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL = 1; + $conf->global->MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL = 0; $leftmenu = 'ab'; $result = (string) dol_eval("(\$leftmenu.'s')", 1, 0); @@ -830,7 +830,7 @@ class SecurityTest extends CommonClassTest // Not allowed - $conf->global->MAIN_DISALLOW_STRING_OBFUSCATION_IN_DOL_EVAL = 0; + $conf->global->MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL = 1; $leftmenu = 'abs'; $result = (string) dol_eval('$leftmenu(-5)', 1, 0); From 63cca7f12804dbbe4d57d27b53dda2514ab7e8b7 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 18 Nov 2025 23:36:26 +0100 Subject: [PATCH 148/387] Doc --- ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 1a0593b3f39..4949f5597bd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -35,7 +35,7 @@ The following changes may create regressions for some external modules, but were * Property ->picto of module descriptors must contains the image extension if it is not a font awesome tag. Example: $this->picto="mymoduleimg.png"; * Stock movement API GET method output variable names has been harmonized with POST input parameter names * $conf is no more allowed into computed formulae. You ca replace use of $conf->currency by NEW Introduce getCurrency() and $conf->global->xxx by getDolGlobalString('xxx') -* Concatenation into computed property of extrafields is off by default. You can enable it with +* Concatenation into computed property of extrafields is off by default. You can enable it with $dolibarr_main_restrict_eval_methods = 'dol_concat' ***** ChangeLog for 22.0.3 compared to 22.0.2 ***** From d858764b1598fea3209cd9591c136068186bbc52 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 19 Nov 2025 03:44:24 +0100 Subject: [PATCH 149/387] Add config param $dolibarr_main_restrict_eval_methods with whitelist of functionsallowed in dol_eval. Advisory GHSA-x3w7-24rq-gvc5 --- ChangeLog | 5 + htdocs/conf/conf.php.example | 6 +- htdocs/core/lib/functions.lib.php | 382 +++++++++++++++--------------- test/phpunit/SecurityTest.php | 44 ++-- 4 files changed, 224 insertions(+), 213 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4949f5597bd..da9066cc231 100644 --- a/ChangeLog +++ b/ChangeLog @@ -36,6 +36,11 @@ The following changes may create regressions for some external modules, but were * Stock movement API GET method output variable names has been harmonized with POST input parameter names * $conf is no more allowed into computed formulae. You ca replace use of $conf->currency by NEW Introduce getCurrency() and $conf->global->xxx by getDolGlobalString('xxx') * Concatenation into computed property of extrafields is off by default. You can enable it with $dolibarr_main_restrict_eval_methods = 'dol_concat' +* Old variable $obj and $object are no more allowed into evaluated strings like computed extrafields or conditions. Use $objectoffield to get current object or $var123 to + instantiate a non existing object. +* The hidden constant MAIN_ALLOW_UNSECURED_SPECIAL_CHARS_IN_DOL_EVAL has been replaced with the + variable $dolibarr_main_allow_unsecured_special_chars_in_dol_eval into conf.php + ***** ChangeLog for 22.0.3 compared to 22.0.2 ***** diff --git a/htdocs/conf/conf.php.example b/htdocs/conf/conf.php.example index 6cfcee65416..b6a041f1032 100644 --- a/htdocs/conf/conf.php.example +++ b/htdocs/conf/conf.php.example @@ -316,11 +316,11 @@ $dolibarr_main_restrict_os_commands='mariadb-dump, mariadb, mysqldump, mysql, pg // ================================== // A whitelist of functions and methods to restrict the commands you can execute in a custom calculated fields, like "computed fields" of // extrafields or string conditions of extrafields. -// Default value: 'getDolGlobalString,getDolGlobalInt,fetchNoCompute,hasRight,isModEnabled,abs,round' +// Default value: 'getDolGlobalString,getDolGlobalInt,getDolCurrency,fetchNoCompute,hasRight,isModEnabled,isStringVarMatching,abs,round,dol_now,preg_match' // Examples: -// $dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,fetchNoCompute,hasRight,isModEnabled,abs,round,dol_concat'; +// $dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,getDolCurrency,fetchNoCompute,hasRight,isModEnabled,isStringVarMatching,abs,min,max,round,dol_now,dol_concat,preg_match'; // -$dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,fetchNoCompute,hasRight,isModEnabled,abs,round'; +$dolibarr_main_restrict_eval_methods='getDolGlobalString,getDolGlobalInt,getDolCurrency,fetchNoCompute,hasRight,isModEnabled,isStringVarMatching,abs,min,max,round,dol_now,preg_match'; // dolibarr_main_disabled_modules // ================================== diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index a4d8817c8c3..cdf81283cc6 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -11510,13 +11510,13 @@ function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = } /** - * Check if a variable with name $var startx with $text. + * Check if a variable with name $var start with $regextext. * Can be used to forge dol_eval() conditions. * - * @param string $var Variable - * @param string $regextext Text that must be a valid regex string - * @param int<0,1> $matchrule 1=Test if start with, 0=Test if equal - * @return boolean|string True or False, text if bad usage. + * @param string $var Variable + * @param string $regextext Text that must be a valid regex string + * @param int<0,1> $matchrule 1=Test if start with, 0=Test if equal + * @return boolean|string True or False, text if bad usage. */ function isStringVarMatching($var, $regextext, $matchrule = 1) { @@ -11531,7 +11531,7 @@ function isStringVarMatching($var, $regextext, $matchrule = 1) return 'This variable is not accessible with dol_eval'; } } else { - return 'This value for matchrule is not implemented'; + return 'This value '.$matchrule.' for param $matchrule is not yet implemented'; } } @@ -11567,7 +11567,7 @@ function verifCond($strToEvaluate, $onlysimplestring = '1') * @param int<0,1> $returnvalue 0=No return (deprecated, used to execute eval($a=something)). 1=Value of eval is returned (used to eval($something)). * @param int<0,1> $hideerrors 1=Hide errors * @param string $onlysimplestring '0' (deprecated, do not use it anymore) = Accept all chars, - * '1' (most common use) = Accept only simple string with char 'a-z0-9\s^$_+-.*>&|=!?():"\',/@';', + * '1' (most common use, recommended) = Accept only simple string with char 'a-z0-9\s^$_+-.*>&|=!?():"\',/@';', * '2' (used for example for the compute property of extrafields) = Accept also '<[]' * @return string Return result of eval (even if type can be int, it is safer to assume string and find all potential typing issues as abs(dol_eval(...)). * @see verifCond(), checkPHPCode() to see sanitizing rules that should be very close. @@ -11582,7 +11582,7 @@ function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1' if (getDolGlobalString("MAIN_USE_DOL_EVAL_NEW")) { return dol_eval_new($s); } else { - return dol_eval_standard($s, $returnvalue, $hideerrors, $onlysimplestring); + return dol_eval_standard($s, $hideerrors, $onlysimplestring); } } @@ -11863,8 +11863,7 @@ function dol_eval_new($s) * Replace eval function to add more security. * This function is called by dol_eval(), itself called by verifCond() or trans() and transnoentitiesnoconv(). * - * @param string $s String to evaluate - * @param int<0,1> $returnvalue 0=No return (deprecated, used to execute eval($a=something)). 1=Value of eval is returned (used to eval($something)). + * @param string $s String to evaluate with eval($something) * @param int<0,1> $hideerrors 1=Hide errors * @param string $onlysimplestring '0' (deprecated, do not use it anymore)=Accept all chars, * '1' (most common use)=Accept only simple string with char 'a-z0-9\s^$_+-.*>&|=!?():"\',/@';', @@ -11873,89 +11872,89 @@ function dol_eval_new($s) * @see verifCond(), checkPHPCode() to see sanitizing rules that should be very close. * @phan-suppress PhanPluginUnsafeEval */ -function dol_eval_standard($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1') +function dol_eval_standard($s, $hideerrors = 1, $onlysimplestring = '1') { // Only this global variables can be read by eval function and returned to caller // The less we have, the better it is. // $conf is excluded. We can read $conf->global->xxx properties with getDolGlobalString(), $conf->currency with getDolCurrency(), $conf->entity with getDolEntity() - global $db; - global $langs, $user, $website, $websitepage; + global $db, $langs, $user, $website, $websitepage; global $action, $mainmenu, $leftmenu; global $mysoc; global $objectoffield; // To allow the use of $objectoffield in computed fields - // Old variables used (deprecated) - global $object; - global $obj; // To get $obj used into list when dol_eval() is used for computed fields and $obj is not yet $objectoffield + // Old variables (deprecated) + if (getDolGlobalString('MAIN_ALLOW_OLD_VAR_OBJ_IN_DOL_EVAL')) { + global $object; + global $obj; // To get $obj used into list when dol_eval() is used for computed fields and $obj is not yet $objectoffield + } - $isObBufferActive = false; // When true, the ObBuffer must be cleaned in the exception handler - if (!in_array($onlysimplestring, array('0', '1', '2'))) { - return "Bad call of dol_eval. Parameter onlysimplestring must be '0' (deprecated), '1' or '2'"; + $isObBufferActive = false; // When true, the ObBuffer must be cleaned in the exception handler + if ($onlysimplestring == '0') { // '0' is deprecated, we process it as the more secured '1' + $onlysimplestring = '1'; + } + if (!in_array($onlysimplestring, array('1', '2'))) { + return "Bad call of dol_eval. Parameter onlysimplestring must be '1' or '2'"; } try { - // Set $dolibarr_main_restrict_eval_methods_array global $dolibarr_main_restrict_eval_methods; - $dolibarr_main_restrict_eval_methods_array = array(); - if (!empty($dolibarr_main_restrict_eval_methods)) { - $dolibarr_main_restrict_eval_methods_array = explode(',', $dolibarr_main_restrict_eval_methods); + + // Set $dolibarr_main_restrict_eval_methods_array + if (!isset($dolibarr_main_restrict_eval_methods)) { + $dolibarr_main_restrict_eval_methods = 'getDolGlobalString,getDolGlobalInt,getDolCurrency,fetchNoCompute,hasRight,isModEnabled,isStringVarMatching,abs,min,max,round,dol_now,preg_match'; + } + //print '$dolibarr_main_restrict_eval_methods = '.$dolibarr_main_restrict_eval_methods."\n"; + $dolibarr_main_restrict_eval_methods_array = explode(',', $dolibarr_main_restrict_eval_methods); + + if (is_array($s) || $s === 'Array') { + return 'Bad string syntax to evaluate (value is Array): ' . var_export($s, true); } // Test on dangerous char (used for RCE), we allow only characters to make PHP variable testing - if ($onlysimplestring == '1' || $onlysimplestring == '2') { - // We must accept with 1: '1 && getDolGlobalInt("doesnotexist1") && getDolGlobalString("MAIN_FEATURES_LEVEL")' - // We must accept with 1: '$user->hasRight("cabinetmed", "read") && !$object->canvas=="patient@cabinetmed"' - // We must accept with 2: (($reloadedobj = new Task($db)) && ($reloadedobj->fetchNoCompute($object->id) <= 99) && ($secondloadedobj = new Project($db)) && ($secondloadedobj->fetchNoCompute($reloadedobj->fk_project) > 0)) ? $secondloadedobj->ref : "Parent project not found" + // We must accept with 1: '1 && getDolGlobalInt("doesnotexist1") && getDolGlobalString("MAIN_FEATURES_LEVEL")' + // We must accept with 1: '$user->hasRight("cabinetmed", "read") && !$objectoffield->canvas == "patient@cabinetmed"' + // We must accept with 2: (($var1 = new Task($db)) && ($var1->fetchNoCompute($object->id) <= 99) && ($var2 = new Project($db)) && ($var2->fetchNoCompute($var1->fk_project) > 0)) ? $var2->ref : "Parent project not found" - // Check if there is dynamic call (first we check chars are all into a whitelist chars) - $specialcharsallowed = '^$_+-.*>&|=!?():"\',/@'; - if ($onlysimplestring == '2') { - $specialcharsallowed .= '<[]'; - } - if (getDolGlobalString('MAIN_ALLOW_UNSECURED_SPECIAL_CHARS_IN_DOL_EVAL')) { - $specialcharsallowed .= getDolGlobalString('MAIN_ALLOW_UNSECURED_SPECIAL_CHARS_IN_DOL_EVAL'); - } - if (preg_match('/[^a-z0-9\s' . preg_quote($specialcharsallowed, '/') . ']/i', $s)) { - if ($returnvalue) { - return 'Bad string syntax to evaluate (found chars that are not chars for a simple one line clean eval string): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (found chars that are not chars for a simple one line clean eval string): ' . $s, LOG_WARNING); - return ''; - } - } + // Check if there is dynamic call (first we check chars are all into a whitelist chars) + $specialcharsallowed = '^$_+-.*>&|=!?():"\',/@'; + if ($onlysimplestring == '2') { + $specialcharsallowed .= '<[]'; // Later we check that < has space before and after + } + global $dolibarr_main_allow_unsecured_special_chars_in_dol_eval; + if (!empty($dolibarr_main_allow_unsecured_special_chars_in_dol_eval)) { + $specialcharsallowed .= (string) $dolibarr_main_allow_unsecured_special_chars_in_dol_eval; + } + if (preg_match('/[^a-z0-9\s' . preg_quote($specialcharsallowed, '/') . ']/i', $s)) { + return 'Bad string syntax to evaluate (found chars that are not chars for a simple one line clean eval string): ' . $s; + } - // Check if we found a ? without a space before and after - $tmps = str_replace(' ? ', '__XXX__', $s); - if (strpos($tmps, '?') !== false) { - if ($returnvalue) { - return 'Bad string syntax to evaluate (The char ? can be used only with a space before and after): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (The char ? can be used only with a space before and after): ' . $s, LOG_WARNING); - return ''; - } - } + // Check if we found a | without a space before and after + /* Disabled to allow preg_match('/(AAA|BBB)/') + $tmps = str_replace(' || ', '__XXX__', $s); + if (strpos($tmps, '|') !== false) { + return 'Bad string syntax to evaluate (The char | can be used only when duplicated || with a space before and after): ' . $s; + } + */ - // Check if there is a < or <= without spaces before/after - if (preg_match('/<=?[^\s]/', $s)) { - if ($returnvalue) { - return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a < or <= without space before and after): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a < or <= without space before and after): ' . $s, LOG_WARNING); - return ''; - } - } + // Check if we found a ? without a space before and after + $tmps = str_replace(' ? ', '__XXX__', $s); + if (strpos($tmps, '?') !== false) { + return 'Bad string syntax to evaluate (The char ? can be used only with a space before and after): ' . $s; + } - // Check if there is dynamic call (first we use black list patterns) - if (preg_match('/\$[\w]*\s*\(/', $s)) { - if ($returnvalue) { - return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a call using "$abc(" or "$abc (" instead of using the direct name of the function): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a call using "$abc(" or "$abc (" instead of using the direct name of the function): ' . $s, LOG_WARNING); - return ''; - } - } + // Check if there is a < or <= without spaces after + if (preg_match('/<=?[^\s]/', $s)) { + return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a < or <= without space after): ' . $s; + } + + // Check if there is dynamic call (first we use black list patterns) + if (preg_match('/\$[\w]*\s*\(/', $s)) { + return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a call using "$abc(" or "$abc (" instead of using the direct name of the function): ' . $s; + } + + if (empty($dolibarr_main_restrict_eval_methods)) { + // If $dolibarr_main_restrict_eval_methods was set to '', we must check if we try dynamic call - // Now we check if we try dynamic call // First we remove white list pattern of using parenthesis then testing if one open parenthesis exists $savescheck = ''; $scheck = $s; @@ -11976,157 +11975,158 @@ function dol_eval_standard($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestr // Now test if it remains 1 open parenthesis. if (strpos($scheck, '(') !== false) { - if ($returnvalue) { - return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found call of a function or method without using the direct name of the function): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found call of a function or method without using the direct name of the function): ' . $s, LOG_WARNING); - return ''; - } - } - - // TODO - // We can exclude $ char that are not in dol_eval global, so that are not: - // $db, $langs, $leftmenu, $topmenu, $user, $langs, $objectoffield, $object, $obj, ..., - } - if (is_array($s) || $s === 'Array') { - if ($returnvalue) { - return 'Bad string syntax to evaluate (value is Array): ' . var_export($s, true); - } else { - dol_syslog('Bad string syntax to evaluate (value is Array): ' . var_export($s, true), LOG_WARNING); - return ''; - } - } - - if (!getDolGlobalString('MAIN_ALLOW_DOUBLE_COLON_IN_DOL_EVAL') && strpos($s, '::') !== false) { - if ($returnvalue) { - return 'Bad string syntax to evaluate (double : char is forbidden without setting MAIN_ALLOW_DOUBLE_COLON_IN_DOL_EVAL): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (double : char is forbidden without setting MAIN_ALLOW_DOUBLE_COLON_IN_DOL_EVAL): ' . $s, LOG_WARNING); - return ''; + return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found call of a function or method without using the direct name of the function): ' . $s; } } if (strpos($s, '`') !== false) { - if ($returnvalue) { - return 'Bad string syntax to evaluate (backtick char is forbidden): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (backtick char is forbidden): ' . $s, LOG_WARNING); - return ''; + return 'Bad string syntax to evaluate (backtick char is forbidden): ' . $s; + } + + // Disallow also concat operator + if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { + if (preg_match('/[^0-9]+\.[^0-9]+/', $s)) { // We refuse . if not between 2 numbers + return 'Bad string syntax to evaluate (dot char is forbidden if not strictly between 2 numbers): ' . $s; } } - // Disallow also concat - if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { - if (preg_match('/[^0-9]+\.[^0-9]+/', $s)) { // We refuse . if not between 2 numbers - if ($returnvalue) { - return 'Bad string syntax to evaluate (dot char is forbidden): ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate (dot char is forbidden): ' . $s, LOG_WARNING); - return ''; - } + // We exclude string using a $ character that are not an expected global or temporary vars, so that are not: + // $db, $langs, $leftmenu, $topmenu, $user, $langs, $objectoffield, $var.... + $savescheck = ''; + $scheck = $s; + while ($scheck && $savescheck != $scheck) { + $savescheck = $scheck; + $scheck = preg_replace('/\$user->hasRight/', '__VARUSERHASRIGHT__', $scheck); + $scheck = preg_replace('/\(\$db\)/', '__VARDB__', $scheck); + $scheck = preg_replace('/\$langs/', '__VARLANGSTRANS__', $scheck); + $scheck = preg_replace('/\$mysoc/', '__VARMYSOC__', $scheck); + $scheck = preg_replace('/\$action/', '__VARACTION__', $scheck); + $scheck = preg_replace('/\$mainmenu/', '__VARMAINMENU__', $scheck); + $scheck = preg_replace('/\$leftmenu/', '__VARLEFTMENU__', $scheck); + $scheck = preg_replace('/\$websitepage/', '__VARWEBSITEPAGE__', $scheck); + $scheck = preg_replace('/\$website/', '__VARWEBSITE__', $scheck); + $scheck = preg_replace('/\$objectoffield/', '__VAROBJECTOFFIELD__', $scheck); + $scheck = preg_replace('/\$var/', '__VARVAR__', $scheck); + + // Now test if it remains 1 $ + if (strpos($scheck, '$') !== false) { + return 'Bad string syntax to evaluate (found use of $ that does not match one of the following pattern: $user->hasRight, ($db), $langs, $mysoc, $action, $mainmenu, $leftmenu, $website, $websitepage, $objectoffield or $var123): ' . $s; } } // We block use of php exec or php file functions - $forbiddenphpstrings = array('$$', '$_', '}[', ')('); + $forbiddenphpstrings = array('}[', ')('); $forbiddenphpstrings = array_merge($forbiddenphpstrings, array('_ENV', '_SESSION', '_COOKIE', '_GET', '_GLOBAL', '_POST', '_REQUEST', 'ReflectionFunction', 'SplFileObject', 'SplTempFileObject')); // We list all forbidden function as keywords we don't want to see (we don't mind it if is "keyword(" or just "keyword", we don't want "keyword" at all) // We must exclude all functions that allow to execute another function. This includes all function that has a parameter with type "callable" to avoid things // like we can do with array_map and its callable parameter: dol_eval('json_encode(array_map(implode("",["ex","ec"]), ["id"]))', 1, 1, '0') $forbiddenphpfunctions = array(); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("override_function", "session_id", "session_create_id", "session_regenerate_id")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function", "call_user_func", "call_user_func_array")); + $forbiddenphpmethods = array(); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("array_all", "array_any", "array_diff_ukey", "array_filter", "array_find", "array_find_key", "array_map", "array_reduce", "array_intersect_uassoc", "array_intersect_ukey", "array_walk", "array_walk_recursive")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("usort", "uasort", "uksort", "preg_replace_callback", "preg_replace_callback_array", "header_register_callback")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("error_log", "set_error_handler", "set_exception_handler", "libxml_set_external_entity_loader", "register_shutdown_function", "register_tick_function", "unregister_tick_function")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("spl_autoload_register", "spl_autoload_unregister", "iterator_apply", "session_set_save_handler")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("forward_static_call", "forward_static_call_array", "register_postsend_function")); + if (empty($dolibarr_main_restrict_eval_methods)) { // If forced to '' + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("override_function", "session_id", "session_create_id", "session_regenerate_id")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function", "call_user_func", "call_user_func_array")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("ob_start")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("array_all", "array_any", "array_diff_ukey", "array_filter", "array_find", "array_find_key", "array_map", "array_reduce", "array_intersect_uassoc", "array_intersect_ukey", "array_walk", "array_walk_recursive")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("usort", "uasort", "uksort", "preg_replace_callback", "preg_replace_callback_array", "header_register_callback")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("error_log", "set_error_handler", "set_exception_handler", "libxml_set_external_entity_loader", "register_shutdown_function", "register_tick_function", "unregister_tick_function")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("spl_autoload_register", "spl_autoload_unregister", "iterator_apply", "session_set_save_handler")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("forward_static_call", "forward_static_call_array", "register_postsend_function")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "dol_eval_new", "dol_eval_standard", "executeCLI", "verifCond", "GETPOST", "dolEncrypt", "dolDecrypt")); // native dolibarr functions - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("readline_completion_function", "readline_callback_handler_install")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_compress_dir", "dol_decode", "dol_dir_list", "dol_dir_list_in_database", "dol_delete_file", "dol_delete_dir", "dol_delete_dir_recursive", "dol_copy", "archiveOrBackupFile")); // more dolibarr functions - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("chdir", "dir", "fopen", "file", "file_exists", "file_get_contents", "file_put_contents", "fget", "fgetc", "fgetcsv", "fputs", "fputscsv", "fpassthru", "fscanf", "fseek", "fwrite", "is_file", "is_dir", "is_link", "mkdir", "opendir", "rmdir", "scandir", "symlink", "touch", "unlink", "umask")); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include")); - if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { // We disallow all function that allow to obfuscate the real name of a function - // @phpcs:ignore - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("base64" . "_" . "decode", "rawurl" . "decode", "url" . "decode", "str" . "_rot13", "hex" . "2bin")); // name of forbidden functions are split to avoid false positive - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concat", "dol_concatdesc")); // native dolibarr functions - } - // Remove from blacklist the function that are into the whitelist - foreach ($forbiddenphpfunctions as $key => $forbiddenphpfunction) { - if (in_array($forbiddenphpfunction, $dolibarr_main_restrict_eval_methods_array)) { - unset($forbiddenphpfunctions[$key]); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("ob_start")); + + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "dol_eval_new", "dol_eval_standard", "executeCLI", "verifCond", "GETPOST", "dolEncrypt", "dolDecrypt")); // native dolibarr functions + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("readline_completion_function", "readline_callback_handler_install")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_compress_dir", "dol_decode", "dol_dir_list", "dol_dir_list_in_database", "dol_delete_file", "dol_delete_dir", "dol_delete_dir_recursive", "dol_copy", "archiveOrBackupFile")); // more dolibarr functions + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("chdir", "dir", "fopen", "file", "file_exists", "file_get_contents", "file_put_contents", "fget", "fgetc", "fgetcsv", "fputs", "fputscsv", "fpassthru", "fscanf", "fseek", "fwrite", "is_file", "is_dir", "is_link", "mkdir", "opendir", "rmdir", "scandir", "symlink", "touch", "unlink", "umask")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include")); + if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { // We disallow all function that allow to obfuscate the real name of a function + // @phpcs:ignore + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("base64" . "_" . "decode", "rawurl" . "decode", "url" . "decode", "str" . "_rot13", "hex" . "2bin", "printf", "sprintf")); // name of forbidden functions are split to avoid false positive + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concat", "dol_concatdesc")); // native dolibarr functions } - } + // Remove from blacklist the function that are into the whitelist + /*foreach ($forbiddenphpfunctions as $key => $forbiddenphpfunction) { + if (in_array($forbiddenphpfunction, $dolibarr_main_restrict_eval_methods_array)) { + unset($forbiddenphpfunctions[$key]); + } + }*/ - $forbiddenphpmethods = array('invoke', 'invokeArgs'); // Method of ReflectionFunction to execute a function - // Remove from blacklist the function that are into the whitelist - foreach ($forbiddenphpmethods as $key => $forbiddenphpmethod) { - if (in_array($forbiddenphpmethod, $dolibarr_main_restrict_eval_methods_array)) { - unset($forbiddenphpmethods[$key]); - } - } + $forbiddenphpmethods = array_merge($forbiddenphpmethods, array('invoke', 'invokeArgs')); // Methods of ReflectionFunction to execute a function + // Remove from blacklist the function that are into the whitelist + /*foreach ($forbiddenphpmethods as $key => $forbiddenphpmethod) { + if (in_array($forbiddenphpmethod, $dolibarr_main_restrict_eval_methods_array)) { + unset($forbiddenphpmethods[$key]); + } + }*/ - $forbiddenphpregex = 'global\s*\$'; - $forbiddenphpregex .= '|'; - $forbiddenphpregex .= '\b(' . implode('|', $forbiddenphpfunctions) . ')\b'; + $forbiddenphpregex = 'global\s*\$'; + $forbiddenphpregex .= '|'; + $forbiddenphpregex .= '\b(' . implode('|', $forbiddenphpfunctions) . ')\b'; - $forbiddenphpmethodsregex = '->(' . implode('|', $forbiddenphpmethods) . ')'; + $forbiddenphpmethodsregex = '->(' . implode('|', $forbiddenphpmethods) . ')'; - // Now scan all forbidden patterns - do { - $oldstringtoclean = $s; - $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s); - $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s); - $s = preg_replace('/' . $forbiddenphpmethodsregex . '/i', '__forbiddenstring__', $s); - //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\(/i', '', $s); // Remove $function( call and $mycall->mymethod( - } while ($oldstringtoclean != $s); + // Now scan all forbidden patterns + do { + $oldstringtoclean = $s; + $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s); + $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s); + $s = preg_replace('/' . $forbiddenphpmethodsregex . '/i', '__forbiddenstring__', $s); + //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\(/i', '', $s); // Remove $function( call and $mycall->mymethod( + } while ($oldstringtoclean != $s); - if (strpos($s, '__forbiddenstring__') !== false) { - dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING); - if ($returnvalue) { + if (strpos($s, '__forbiddenstring__') !== false) { + dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING); return 'Bad string syntax to evaluate: ' . $s; - } else { - dol_syslog('Bad string syntax to evaluate: ' . $s); - return ''; } - } + } else { + // Accept only white-listed allowed function and classes + // TODO Get all pattern '/([\s\w]+)\(/', then check that $reg[1] is a defined class or a function into a given list + $pattern = '/([\s\w\'\]\"]+)\(/'; + $matches = array(); + preg_match_all($pattern, $s, $matches); - // Accept only white-listed allowed function and classes - if (!empty($dolibarr_main_restrict_eval_methods)) { - // TODO Get all pattern '/(\w+)\(/', then check that $reg[1] is a defined class or a function into a given list + if (!empty($matches)) { + foreach ($matches[1] as $m) { + $m = trim($m); + if (empty($m)) { + continue; + } + $reg = array(); + if (!preg_match('/new ([A-Z][\w]+)/i', $m, $reg)) { + if (!in_array($m, $dolibarr_main_restrict_eval_methods_array)) { + if ($m != "'" && $m != '"') { + dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING); + return 'Bad string syntax to evaluate. A function or method "'.$m.'" was called and is not into the parameter $dolibarr_main_restrict_eval_methods of white-listed functions and methods: ' . $s; + } + } + } else { + if ($reg[1] == 'ReflectionFunction') { + dol_syslog('Bad string syntax to evaluate: Class ReflectionFunction is not allowed. ' . $s, LOG_WARNING); + return 'Bad string syntax to evaluate. Class ReflectionFunction is not allowed. ' . $s; + } + } + } + } } //print $s."
\n"; - if ($returnvalue) { - ob_start(); // An evaluation has no reason to output data - $isObBufferActive = true; - $tmps = $hideerrors ? @eval('return ' . $s . ';') : eval('return ' . $s . ';'); - $tmpo = ob_get_clean(); - $isObBufferActive = false; - if ($tmpo) { - print 'Bad string syntax to evaluate. Some data were output when it should not when evaluating: ' . $s; - } - return $tmps; - } else { - dol_syslog('Do not use anymore dol_eval with param returnvalue=0', LOG_WARNING); - if ($hideerrors) { - @eval($s); - } else { - eval($s); - } - return ''; + ob_start(); // An evaluation has no reason to output data + $isObBufferActive = true; + $tmps = $hideerrors ? @eval('return ' . $s . ';') : eval('return ' . $s . ';'); + $tmpo = ob_get_clean(); + $isObBufferActive = false; + if ($tmpo) { + print 'Bad string syntax to evaluate. Some data were output when it should not when evaluating: ' . $s; } + return $tmps; } catch (Error $e) { if ($isObBufferActive) { // Clean up buffer which was left behind due to exception. @@ -12136,11 +12136,7 @@ function dol_eval_standard($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestr $error = 'dol_eval try/catch error : '; $error .= $e->getMessage(); dol_syslog($error, LOG_WARNING); - if ($returnvalue) { - return 'Exception during evaluation: ' . $s; - } else { - return ''; - } + return 'Exception during evaluation: ' . $s; } } diff --git a/test/phpunit/SecurityTest.php b/test/phpunit/SecurityTest.php index 51a022efc34..da29c63e615 100644 --- a/test/phpunit/SecurityTest.php +++ b/test/phpunit/SecurityTest.php @@ -617,6 +617,12 @@ class SecurityTest extends CommonClassTest $conf->global->MAIN_ALLOW_DOUBLE_COLON_IN_DOL_EVAL = 0; $conf->global->MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL = 1; + // We force $dolibarr_main_restrict_eval_methods to empty, so the code will use the old black-list patterns. + global $dolibarr_main_restrict_eval_methods; + print "\ndolibarr_main_restrict_eval_methods = ".$dolibarr_main_restrict_eval_methods."\n"; + + //$dolibarr_main_restrict_eval_methods = array(); + //$resulttest = dol_eval('((getDolGlobalString("MAIN_USE_ADVANCED_PERMS") ? $user->hasRight("user","group_advance","read") : $user->hasRight("user","user","lire")) || $user->admin) && !(isModEnabled("multicompany") && $conf->entity > 1 && getDolGlobalString("MULTICOMPANY_TRANSVERSE_MODE"))', 1, 0); //print "resulttest = ".$resulttest."\n"; @@ -630,22 +636,22 @@ class SecurityTest extends CommonClassTest print "result2 = ".$result."\n"; $this->assertFalse($result); - $s = '((($reloadedobj = new ClassThatDoesNotExists($db)) && ($reloadedobj->fetchNoCompute($objectoffield->fk_product) > 0)) ? \'1\' : \'0\')'; + $s = '((($var1 = new ClassThatDoesNotExists($db)) && ($var1->fetchNoCompute($objectoffield->fk_product) > 0)) ? \'1\' : \'0\')'; $result3a = dol_eval($s, 1, 1, '2'); print "result3a = ".$result3a."\n"; $this->assertStringContainsString('Exception during evaluation: '.$s, $result3a); - $s = '((($reloadedobj = new Project($db)) && ($reloadedobj->fetchNoCompute($objectoffield->fk_product) > 0)) ? \'1\' : \'0\')'; + $s = '((($var1 = new Project($db)) && ($var1->fetchNoCompute($objectoffield->fk_product) > 0)) ? \'1\' : \'0\')'; $result3b = dol_eval($s, 1, 1, '2'); print "result3b = ".$result."\n"; $this->assertEquals('0', $result3b); - $s = '(($reloadedobj = new Task($db)) && ($reloadedobj->fetchNoCompute($object->id) > 0) && ($secondloadedobj = new Project($db)) && ($secondloadedobj->fetchNoCompute($reloadedobj->fk_project) > 0)) ? $secondloadedobj->ref : "Parent project not found"'; + $s = '(($var1 = new Task($db)) && ($var1->fetchNoCompute($objectoffield->id) > 0) && ($var2 = new Project($db)) && ($var2->fetchNoCompute($var1->fk_project) > 0)) ? $var2->ref : "Parent project not found"'; $result = (string) dol_eval($s, 1, 1, '2'); print "result3c = ".$result."\n"; $this->assertEquals('Parent project not found', $result); - $s = '(($reloadedobj = new Task($db)) && ($reloadedobj->fetchNoCompute($object->id) > 0) && ($secondloadedobj = new Project($db)) && ($secondloadedobj->fetchNoCompute($reloadedobj->fk_project) > 0)) ? $secondloadedobj->ref : \'Parent project not found\''; + $s = '(($var1 = new Task($db)) && ($var1->fetchNoCompute($objectoffield->id) > 0) && ($var2 = new Project($db)) && ($var2->fetchNoCompute($var1->fk_project) > 0)) ? $var2->ref : \'Parent project not found\''; $result = (string) dol_eval($s, 1, 1, '2'); print "result4 = ".$result."\n"; $this->assertEquals('Parent project not found', $result, 'Test 4'); @@ -659,12 +665,12 @@ class SecurityTest extends CommonClassTest print "result6 = ".$result."\n"; $this->assertEquals('1', $result, 'Test 5'); + /* $s = 'MyClass::MyMethod()'; $result = dol_eval($s, 1, 1, '2'); print "result7 = ".$result."\n"; - $this->assertStringContainsString('Bad string syntax to evaluate (double : char is forbidden without setting MAIN_ALLOW_DOUBLE_COLON_IN_DOL_EVAL)', $result); - - + $this->assertStringContainsString('Bad string syntax to evaluate (double : char is forbidden without setting MAIN_ALLOW_DOUBLE_COLON_IN_DOL_EVAL)', $result, 'The string was not detected as evil'); + */ /* not allowed. Not a one line eval string $result = (string) dol_eval('if ($a == 1) { }', 1, 1); @@ -718,21 +724,21 @@ class SecurityTest extends CommonClassTest print "result7 = ".$result."\n"; $this->assertStringContainsString('Bad string syntax to evaluate (found chars that are not chars for a simple one line clean eval string)', $result, 'The string was not detected as evil'); - $result = (string) dol_eval('$a=exec("ls")', 1, 1); + $result = (string) dol_eval('$var1=exec("ls")', 1, 1); print "result7 = ".$result."\n"; - $this->assertStringContainsString('Bad string syntax to evaluate (mode 1, found call of a function or method without using the direct name of the function)', $result, 'The string was not detected as evil'); + $this->assertStringContainsString('Bad string syntax to evaluate', $result, 'The string was not detected as evil'); - $result = (string) dol_eval('$a=exec(\'ls\')', 1, 1); + $result = (string) dol_eval('$var1=exec(\'ls\')', 1, 1); print "result7 = ".$result."\n"; - $this->assertStringContainsString('Bad string syntax to evaluate (mode 1, found call of a function or method without using the direct name of the function)', $result, 'The string was not detected as evil'); + $this->assertStringContainsString('Bad string syntax to evaluate', $result, 'The string was not detected as evil'); - $result = (string) dol_eval('$a=exec ("ls")', 1, 1); + $result = (string) dol_eval('$var1=exec ("ls")', 1, 1); print "result8 = ".$result."\n"; $this->assertStringContainsString('Bad string syntax to evaluate', $result, 'The string was not detected as evil'); $result = (string) dol_eval("strrev('metsys') ('whoami')", 1, 1); print "result8b = ".$result."\n"; - $this->assertStringContainsString('Bad string syntax to evaluate (mode 1, found call of a function or method without using the direct name of the function)', $result, 'The string was not detected as evil'); + $this->assertStringContainsString('Bad string syntax to evaluate', $result, 'The string was not detected as evil'); $conf->global->MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL = 0; @@ -746,11 +752,11 @@ class SecurityTest extends CommonClassTest $result = (string) dol_eval("('ex'.'ec')('ls')", 1, 0); // This will execute exec of ls print "result11 = ".$result."\n"; - $this->assertStringContainsString('Bad string syntax to evaluate (mode 1, found call of a function or method without using the direct name of the function)', $result, 'The string was not detected as evil'); + $this->assertStringContainsString('Bad string syntax to evaluate', $result, 'The string was not detected as evil'); $result = (string) dol_eval("('ex'.'ec') /* */ (/* */'ls')", 1, 0); // This will execute exec of ls print "result11 = ".$result."\n"; - $this->assertStringContainsString('Bad string syntax to evaluate (mode 1, found call of a function or method without using the direct name of the function)', $result, 'The string was not detected as evil'); + $this->assertStringContainsString('Bad string syntax to evaluate', $result, 'The string was not detected as evil'); $result = (string) dol_eval("sprintf(\"%s%s\", \"ex\", \"ec\")('echo abc')", 1, 0); print "result12 = ".$result."\n"; @@ -825,7 +831,7 @@ class SecurityTest extends CommonClassTest $leftmenu = 'ab'; $result = (string) dol_eval("(\$leftmenu.'s')", 1, 0); print "resultconcat3 = ".$result."\n"; - $this->assertStringContainsString('Bad string syntax to evaluate (dot char is forbidden)', $result, 'Test concat - The string was not reported as a bad syntax when it should'); + $this->assertStringContainsString('Bad string syntax to evaluate (dot char is forbidden if not strictly between 2 numbers)', $result, 'Test concat - The string was not reported as a bad syntax when it should'); // Not allowed @@ -847,11 +853,15 @@ class SecurityTest extends CommonClassTest $result = (string) dol_eval('\'exec\'("aaa")', 1, 0); print "result23 = ".$result."\n"; - $this->assertStringContainsString('Bad string syntax to evaluate', json_encode($result), 'Test 23 - The string was not detected as evil - Can\'t find the string Bad string syntax when i should'); + $this->assertStringContainsString('Bad string syntax to evaluate', json_encode($result), 'Test 23 - The string was not detected as evil - Can\'t find the string Bad string syntax when it should'); $result = (string) dol_eval('1 + 2 ', 1, 0, '2'); print "result24 = ".$result."\n"; $this->assertStringContainsString('Bad string syntax to evaluate (The char ? can be used only with a space before and after)', json_encode($result), 'Test 24 - The string was not detected as evil - Can\'t find the string Bad string syntax when i should'); + + $result = (string) dol_eval('$$a', 1, 0); + print "result25 = ".$result."\n"; + $this->assertStringContainsString('Bad string syntax to evaluate', json_encode($result), 'Test 25 - The string was not detected as evil - Can\'t find the string Bad string syntax when i should'); } From 613a4bab2db24001a183d8e248b8042642e0f9fa Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 19 Nov 2025 10:57:11 +0100 Subject: [PATCH 150/387] Fix CI --- htdocs/adherents/agenda.php | 2 +- htdocs/blockedlog/admin/blockedlog_archives.php | 2 +- htdocs/contact/agenda.php | 4 +++- htdocs/core/class/html.formactions.class.php | 2 +- htdocs/core/lib/files.lib.php | 8 ++++---- htdocs/core/lib/functions.lib.php | 5 +++-- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/htdocs/adherents/agenda.php b/htdocs/adherents/agenda.php index 6fb7f876911..2cafcc707d0 100644 --- a/htdocs/adherents/agenda.php +++ b/htdocs/adherents/agenda.php @@ -47,7 +47,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; $langs->loadLangs(array('companies', 'members')); $action = GETPOST('action', 'aZ09'); -$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'thirdpartyagenda'; +$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : getDolDefaultContextPage(__FILE__); if (GETPOSTISARRAY('actioncode')) { $actioncode = GETPOST('actioncode', 'array:alpha', 3); diff --git a/htdocs/blockedlog/admin/blockedlog_archives.php b/htdocs/blockedlog/admin/blockedlog_archives.php index 66d6e736b95..7dc47a0fbfc 100644 --- a/htdocs/blockedlog/admin/blockedlog_archives.php +++ b/htdocs/blockedlog/admin/blockedlog_archives.php @@ -505,7 +505,7 @@ if (GETPOST('withtab', 'alpha')) { if ($action == 'deletefile') { $langs->load("companies"); // Need for string DeleteFile+ConfirmDeleteFiles print $form->formconfirm( - $_SERVER["PHP_SELF"].'?id='.$object->id.'&urlfile='.urlencode(GETPOST("urlfile")).'&linkid='.GETPOSTINT('linkid').(empty($param) ? '' : $param), + $_SERVER["PHP_SELF"].'?urlfile='.urlencode(GETPOST("urlfile")).'&linkid='.GETPOSTINT('linkid').(empty($param) ? '' : $param), $langs->trans('DeleteFile'), $langs->trans('ConfirmDeleteFile'), 'confirm_deletefile', diff --git a/htdocs/contact/agenda.php b/htdocs/contact/agenda.php index 60430099f8b..5e94ccc157c 100644 --- a/htdocs/contact/agenda.php +++ b/htdocs/contact/agenda.php @@ -291,7 +291,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { $out .= '&originid='.$objthirdparty->id.($objthirdparty->id > 0 ? '&socid='.$objthirdparty->id : ''); } $out .= (!empty($objcon->id) ? '&contactid='.$objcon->id : '').'&origin=contact&originid='.$object->id.'&backtopage='.urlencode($_SERVER['PHP_SELF'].($objcon->id > 0 ? '?id='.$objcon->id : '')); - $out .= '&datep='.urlencode(dol_print_date(dol_now(), 'dayhourlog'), 'tzuserrel'); + $out .= '&datep='.urlencode(dol_print_date(dol_now(), 'dayhourlog', 'tzuserrel')); } if ($user->hasRight('agenda', 'myactions', 'create') || $user->hasRight('agenda', 'allactions', 'create')) { @@ -342,6 +342,8 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { $titlelist = $langs->trans("Actions").(is_numeric($nbEvent) ? '('.$nbEvent.')' : ''); } + $morehtmlright = ''; + print_barre_liste($titlelist, 0, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', 0, -1, '', 0, $morehtmlright, '', 0, 1, 0); // List of all actions diff --git a/htdocs/core/class/html.formactions.class.php b/htdocs/core/class/html.formactions.class.php index 8935346d4eb..52a9adcaa44 100644 --- a/htdocs/core/class/html.formactions.class.php +++ b/htdocs/core/class/html.formactions.class.php @@ -65,7 +65,7 @@ class FormActions * @param integer $onlyselect 0=Standard, 1=Hide percent of completion and force usage of a select list, 2=Same than 1 and add "Incomplete (Todo+Running) * @param string $morecss More css on select field * @param int $nooutput 1=No output, return string. 0=Print on output - * @return void + * @return string|void */ public function form_select_status_action($formname, $selected, $canedit = 1, $htmlname = 'complete', $showempty = 0, $onlyselect = 0, $morecss = 'maxwidth100', $nooutput = 0) { diff --git a/htdocs/core/lib/files.lib.php b/htdocs/core/lib/files.lib.php index 32559cd332e..7c141daa4b4 100644 --- a/htdocs/core/lib/files.lib.php +++ b/htdocs/core/lib/files.lib.php @@ -47,9 +47,9 @@ function dol_basename($pathfile) * @param string $utf8_path Starting path from which to search. This is a full path. * @param string $types Can be "directories", "files", or "all" * @param int $recursive Determines whether subdirectories are searched - * @param string|string[] $filter Regex or Array of Regex filter to restrict list. The regex value must be escaped for '/' by doing preg_quote($var,'/'), since this char is used for preg_match function, - * but must NOT contains the start and end '/'. Filter is checked into basename only. - * @param string|string[] $excludefilter Array of Regex for exclude filter (example: array('(\.meta|_preview.*\.png)$','^\.')). Exclude is checked both into fullpath and into basename (So '^xxx' may exclude 'xxx/dirscanned/...' and dirscanned/xxx'). + * @param string|string[]|null $filter Regex or Array of Regex filter to restrict list. The regex value must be escaped for '/' by doing preg_quote($var,'/'), since this char is used for preg_match function, + * but must NOT contains the start and end '/'. Filter is checked into basename only. + * @param string|string[]|null $excludefilter Array of Regex for exclude filter (example: array('(\.meta|_preview.*\.png)$','^\.')). Exclude is checked both into fullpath and into basename (So '^xxx' may exclude 'xxx/dirscanned/...' and dirscanned/xxx'). * @param string $sortcriteria Sort criteria ('','fullname','relativename','name','date','size') * @param int $sortorder Sort order (SORT_ASC, SORT_DESC) * @param int $mode 0=Return array minimum keys loaded (faster), 1=Force all keys like date and size to be loaded (slower), 2=Force load of date only, 3=Force load of size only, 4=Force load of perm @@ -70,7 +70,7 @@ function dol_dir_list($utf8_path, $types = "all", $recursive = 0, $filter = "", // Verify filters (only on the first call of the function) $filter_ok = true; - if (!is_array($filter)) { + if (!empty($filter) && !is_array($filter)) { if (strlen($filter) > 25000) { // Note that limit depends on syntax of filter dol_syslog("Value for filter is too large", LOG_ERR); $filter_ok = false; diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index cdf81283cc6..1eeace90080 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -126,7 +126,7 @@ if (!function_exists('str_contains')) { * Return the full path of the directory where a module (or an object of a module) stores its files. * Path may depends on the entity if a multicompany module is enabled. * - * @param CommonObject $object Dolibarr common object. + * @param CommonObject|BlockedLog $object Dolibarr common object. * @param string $module Override object element, for example to use 'mycompany' instead of 'societe' * @param int $forobject Return the more complete path for the given object instead of for the module only. * @param string $mode 'output' (full main dir) or 'outputrel' (relative dir) or 'temp' (full dir for temporary files) or 'version' (full dir for archived files) @@ -8487,7 +8487,8 @@ function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, // Allow an external module to bypass the calculation of prices $parameters = array('vatvalue' => $vatvalue, 'vatrule' => $vatrule); - $tmpobject = null; $tmpaction = null; + $tmpobject = null; $tmpaction = ''; + // @phan-ignore-next-line PhanPluginConstantVariableNull $reshook = $hookmanager->executeHooks('get_default_tva', $parameters, $tmpobject, $tmpaction); if ($reshook > 0 && !empty($hookmanager->resArray['vatvalue'])) { $vatvalue = $hookmanager->resArray['vatvalue']; From 45184e9741627ae132631f1f0b419b68c5dba138 Mon Sep 17 00:00:00 2001 From: MDW Date: Wed, 19 Nov 2025 10:58:00 +0100 Subject: [PATCH 151/387] Qual: Update phan baseline (#36318) # Qual: Update phan baseline Remove fixed notices from exceptions. --- dev/tools/phan/baseline.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/dev/tools/phan/baseline.txt b/dev/tools/phan/baseline.txt index fce5357e334..e97b76bdf14 100644 --- a/dev/tools/phan/baseline.txt +++ b/dev/tools/phan/baseline.txt @@ -76,7 +76,6 @@ return [ 'htdocs/core/ajax/selectobject.php' => ['PhanTypeMismatchArgumentNullable'], 'htdocs/core/class/CMailFile.class.php' => ['PhanTypeMismatchArgument'], 'htdocs/core/class/canvas.class.php' => ['PhanUndeclaredMethod'], - 'htdocs/core/class/ccountry.class.php' => ['PhanUndeclaredProperty'], 'htdocs/core/class/cgenericdic.class.php' => ['PhanUndeclaredProperty'], 'htdocs/core/class/commonobject.class.php' => ['PhanParamTooMany', 'PhanTypeMismatchArgument', 'PhanUndeclaredProperty'], 'htdocs/core/class/commonpeople.class.php' => ['PhanUndeclaredProperty'], @@ -146,7 +145,6 @@ return [ 'htdocs/core/tpl/objectline_view.tpl.php' => ['PhanUndeclaredProperty'], 'htdocs/core/tpl/passwordreset.tpl.php' => ['PhanUndeclaredGlobalVariable'], 'htdocs/core/tpl/resource_view.tpl.php' => ['PhanUndeclaredProperty'], - 'htdocs/core/triggers/interface_20_modWorkflow_WorkflowManager.class.php' => ['PhanUndeclaredProperty'], 'htdocs/core/triggers/interface_50_modAgenda_ActionsAuto.class.php' => ['PhanUndeclaredProperty'], 'htdocs/delivery/class/delivery.class.php' => ['PhanUndeclaredProperty'], 'htdocs/emailcollector/class/emailcollector.class.php' => ['PhanUndeclaredProperty'], From 37dca42aeeb5c6cd131dada7c64967211753297b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 19 Nov 2025 11:05:26 +0100 Subject: [PATCH 152/387] Doc --- htdocs/core/class/extrafields.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/class/extrafields.class.php b/htdocs/core/class/extrafields.class.php index 4149a4ebbe4..823820faee1 100644 --- a/htdocs/core/class/extrafields.class.php +++ b/htdocs/core/class/extrafields.class.php @@ -1987,8 +1987,8 @@ class ExtraFields $element = 'project'; } - //$objectdesc = $param_list[0]; // Example: 'ObjectName:classPath:1:(status:=:1)' Replaced by next line: this was propagated also a filter by ajax call that was blocked by some WAF - $objectdesc = $tmparray[0]; // Example: 'ObjectName:classPath' To not propagate any filter (selectForForms do ajax call and propagating SQL filter is blocked by some WAF). Also we should use the one into the definition in the ->fields of $elem if found. + //$objectdesc = $param_list[0]; // Example: 'ObjectName:classPath:1:(status:=:1)' Replaced by next line: old line propagated also the filter to ajax call that was blocked by some WAF + $objectdesc = $tmparray[0]; // Example: 'ObjectName:classPath' To not propagate any filter (selectForForms do ajax call and propagating SQL filter is blocked by some WAF). Also we should use the filter into the definition in the ->fields of $elem if found. $objectfield = $element.':options_'.$key; // Example: 'actioncomm:options_fff' To be used in priority to know object linked with all its definition (including filters) $out = $form->selectForForms($objectdesc, $keyprefix.$key.$keysuffix, $value, $showempty, '', '', $morecss, '', 0, 0, '', $objectfield); From 75d1f27730ba338e18b2a15be784efa80f83ee5d Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 19 Nov 2025 11:16:24 +0100 Subject: [PATCH 153/387] Fix CI --- htdocs/core/class/html.formfile.class.php | 2 +- htdocs/core/lib/functions.lib.php | 2 +- htdocs/core/lib/price.lib.php | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index 58065dbc45f..649f3f512e6 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -160,7 +160,7 @@ class FormFile * @param int $sectionid If upload must be done inside a particular ECM section (is sectionid defined, sectiondir must not be) * @param int $perm Value of permission to allow upload * @param int $size Length of input file area. Deprecated. - * @param ?CommonObject $object Object to use (when attachment is done on an element) + * @param ?CommonObject|BlockedLog $object Object to use (when attachment is done on an element) * @param string $options Add an option column * @param int<0,1> $useajax Use fileupload ajax (0=never, 1=if enabled, 2=always whatever is option). * Deprecated 2 should never be used and if 1 is used, option should not be enabled. diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 1eeace90080..43057a0dcae 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -8488,7 +8488,7 @@ function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, // Allow an external module to bypass the calculation of prices $parameters = array('vatvalue' => $vatvalue, 'vatrule' => $vatrule); $tmpobject = null; $tmpaction = ''; - // @phan-ignore-next-line PhanPluginConstantVariableNull + // @phan-ignore-next-line $reshook = $hookmanager->executeHooks('get_default_tva', $parameters, $tmpobject, $tmpaction); if ($reshook > 0 && !empty($hookmanager->resArray['vatvalue'])) { $vatvalue = $hookmanager->resArray['vatvalue']; diff --git a/htdocs/core/lib/price.lib.php b/htdocs/core/lib/price.lib.php index 1a2908fc67c..005834a0995 100644 --- a/htdocs/core/lib/price.lib.php +++ b/htdocs/core/lib/price.lib.php @@ -458,7 +458,8 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt // Allow an external module to bypass the calculation of prices $parameters = array('result' => $result); - $tmpobject = null; $tmpaction = null; + $tmpobject = null; $tmpaction = ''; + // @phan-ignore-next-line $reshook = $hookmanager->executeHooks('calcul_price_total', $parameters, $tmpobject, $tmpaction); if ($reshook > 0 && !empty($hookmanager->resArray['result'])) { $result = $hookmanager->resArray['result']; From 8a8ed7fa67999038e781c26563d8348473d77f6b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 19 Nov 2025 11:28:58 +0100 Subject: [PATCH 154/387] Fix CI --- htdocs/core/lib/functions.lib.php | 2 +- htdocs/core/lib/price.lib.php | 2 +- htdocs/core/menus/standard/auguria.lib.php | 1 - htdocs/core/menus/standard/eldy.lib.php | 1 - htdocs/expedition/class/expedition.class.php | 9 +++++++-- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 43057a0dcae..3e0e409c16b 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -8488,7 +8488,7 @@ function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, // Allow an external module to bypass the calculation of prices $parameters = array('vatvalue' => $vatvalue, 'vatrule' => $vatrule); $tmpobject = null; $tmpaction = ''; - // @phan-ignore-next-line + // @phan-suppress-next-line $reshook = $hookmanager->executeHooks('get_default_tva', $parameters, $tmpobject, $tmpaction); if ($reshook > 0 && !empty($hookmanager->resArray['vatvalue'])) { $vatvalue = $hookmanager->resArray['vatvalue']; diff --git a/htdocs/core/lib/price.lib.php b/htdocs/core/lib/price.lib.php index 005834a0995..1abbb973a80 100644 --- a/htdocs/core/lib/price.lib.php +++ b/htdocs/core/lib/price.lib.php @@ -459,7 +459,7 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt // Allow an external module to bypass the calculation of prices $parameters = array('result' => $result); $tmpobject = null; $tmpaction = ''; - // @phan-ignore-next-line + // @phan-supress-next-line $reshook = $hookmanager->executeHooks('calcul_price_total', $parameters, $tmpobject, $tmpaction); if ($reshook > 0 && !empty($hookmanager->resArray['result'])) { $result = $hookmanager->resArray['result']; diff --git a/htdocs/core/menus/standard/auguria.lib.php b/htdocs/core/menus/standard/auguria.lib.php index 416fefdace3..8bbfebe19e1 100644 --- a/htdocs/core/menus/standard/auguria.lib.php +++ b/htdocs/core/menus/standard/auguria.lib.php @@ -191,7 +191,6 @@ function print_auguria_menu($db, $atarget, $type_user, &$tabMenu, &$menu, $noout foreach ($menu->liste as $menuval) { print_start_menu_entry_auguria($menuval['idsel'], $menuval['classname'], $menuval['enabled']); - // @phan-ignore-next-line // @phpstan-ignore-next-line print_text_menu_entry_auguria($menuval['titre'], $menuval['enabled'], ($menuval['url'] != '#' ? DOL_URL_ROOT : '').$menuval['url'], $menuval['id'], $menuval['idsel'], $menuval['classname'], ($menuval['target'] ? $menuval['target'] : $atarget), $menuval); print_end_menu_entry_auguria($menuval['enabled']); diff --git a/htdocs/core/menus/standard/eldy.lib.php b/htdocs/core/menus/standard/eldy.lib.php index 1490cf5d460..b24aa604341 100644 --- a/htdocs/core/menus/standard/eldy.lib.php +++ b/htdocs/core/menus/standard/eldy.lib.php @@ -608,7 +608,6 @@ function print_eldy_menu($db, $atarget, $type_user, &$tabMenu, &$menu, $noout = //var_dump($menu->liste); foreach ($menu->liste as $menuval) { print_start_menu_entry($menuval['idsel'], $menuval['classname'], $menuval['enabled']); - // @phan-ignore-next-line // @phpstan-ignore-next-line print_text_menu_entry($menuval['titre'], $menuval['enabled'], (($menuval['url'] != '#' && !preg_match('/^(http:\/\/|https:\/\/)/i', $menuval['url'])) ? DOL_URL_ROOT : '').$menuval['url'], $menuval['id'], $menuval['idsel'], $menuval['classname'], ($menuval['target'] ? $menuval['target'] : $atarget), $menuval); print_end_menu_entry($menuval['enabled']); diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 0a8e32b2c36..60d870b4ef6 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -109,6 +109,12 @@ class Expedition extends CommonObject */ public $fk_user_author; + /** + * @var ?int ID of user that validates + * @deprecated use $user_validation_id + */ + public $fk_user_valid; + /** * @var ?int */ @@ -1551,8 +1557,7 @@ class Expedition extends CommonObject if (isset($this->fk_user_author)) { $this->fk_user_author = (int) $this->fk_user_author; } - if (isset($this->fk_user_valid)) { // @phan-ignore-current-line PhanUndeclaredProperty - // If set, then accept @phan-ignore-next-line PhanUndeclaredProperty + if (isset($this->fk_user_valid)) { $this->fk_user_valid = (int) $this->fk_user_valid; } if (isset($this->fk_delivery_address)) { From dd39b91e5a04a4a8bbd153246b37f334bc723efd Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 19 Nov 2025 11:40:02 +0100 Subject: [PATCH 155/387] Fix ci --- htdocs/core/class/html.formfile.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index 649f3f512e6..4349a4bfc82 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -160,7 +160,7 @@ class FormFile * @param int $sectionid If upload must be done inside a particular ECM section (is sectionid defined, sectiondir must not be) * @param int $perm Value of permission to allow upload * @param int $size Length of input file area. Deprecated. - * @param ?CommonObject|BlockedLog $object Object to use (when attachment is done on an element) + * @param CommonObject|BlockedLog|null $object Object to use (when attachment is done on an element) * @param string $options Add an option column * @param int<0,1> $useajax Use fileupload ajax (0=never, 1=if enabled, 2=always whatever is option). * Deprecated 2 should never be used and if 1 is used, option should not be enabled. From 0a6489a55ffa4cd8349785248af8d55f1e67538c Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 19 Nov 2025 11:59:33 +0100 Subject: [PATCH 156/387] Fix CI --- htdocs/core/lib/functions.lib.php | 2 +- htdocs/core/lib/price.lib.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 3e0e409c16b..cd6647fdf7a 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -8488,7 +8488,7 @@ function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, // Allow an external module to bypass the calculation of prices $parameters = array('vatvalue' => $vatvalue, 'vatrule' => $vatrule); $tmpobject = null; $tmpaction = ''; - // @phan-suppress-next-line + // @phan-suppress PhanPluginConstantVariableNull $reshook = $hookmanager->executeHooks('get_default_tva', $parameters, $tmpobject, $tmpaction); if ($reshook > 0 && !empty($hookmanager->resArray['vatvalue'])) { $vatvalue = $hookmanager->resArray['vatvalue']; diff --git a/htdocs/core/lib/price.lib.php b/htdocs/core/lib/price.lib.php index 1abbb973a80..c4fa89f3077 100644 --- a/htdocs/core/lib/price.lib.php +++ b/htdocs/core/lib/price.lib.php @@ -459,7 +459,7 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt // Allow an external module to bypass the calculation of prices $parameters = array('result' => $result); $tmpobject = null; $tmpaction = ''; - // @phan-supress-next-line + // @phan-suppress PhanPluginConstantVariableNull $reshook = $hookmanager->executeHooks('calcul_price_total', $parameters, $tmpobject, $tmpaction); if ($reshook > 0 && !empty($hookmanager->resArray['result'])) { $result = $hookmanager->resArray['result']; From 5ccab94697b01c6cddf2ba928b0a348fed2dfa31 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 19 Nov 2025 12:12:07 +0100 Subject: [PATCH 157/387] Fix CI --- htdocs/core/lib/functions.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index cd6647fdf7a..7c46aa73abe 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -12093,7 +12093,7 @@ function dol_eval_standard($s, $hideerrors = 1, $onlysimplestring = '1') $matches = array(); preg_match_all($pattern, $s, $matches); - if (!empty($matches)) { + if (count($matches)) { foreach ($matches[1] as $m) { $m = trim($m); if (empty($m)) { From 77e66216dc62136a05e0eacb9d8b4a574b421c6c Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 19 Nov 2025 12:15:48 +0100 Subject: [PATCH 158/387] Fix ci --- htdocs/core/lib/functions.lib.php | 4 ++-- htdocs/core/lib/price.lib.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 7c46aa73abe..1df7a74e28c 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -8488,8 +8488,8 @@ function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, // Allow an external module to bypass the calculation of prices $parameters = array('vatvalue' => $vatvalue, 'vatrule' => $vatrule); $tmpobject = null; $tmpaction = ''; - // @phan-suppress PhanPluginConstantVariableNull - $reshook = $hookmanager->executeHooks('get_default_tva', $parameters, $tmpobject, $tmpaction); + // @phan-suppress-next-line PhanPluginConstantVariableNull + $reshook = $hookmanager->executeHooks('get_default_tva', $parameters, $tmpobject, $tmpaction); // @phan-suppress-current-line PhanPluginConstantVariableNull if ($reshook > 0 && !empty($hookmanager->resArray['vatvalue'])) { $vatvalue = $hookmanager->resArray['vatvalue']; $vatrule = $hookmanager->resArray['vatrule']; // For information diff --git a/htdocs/core/lib/price.lib.php b/htdocs/core/lib/price.lib.php index c4fa89f3077..1d39ec0c7a7 100644 --- a/htdocs/core/lib/price.lib.php +++ b/htdocs/core/lib/price.lib.php @@ -459,8 +459,8 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt // Allow an external module to bypass the calculation of prices $parameters = array('result' => $result); $tmpobject = null; $tmpaction = ''; - // @phan-suppress PhanPluginConstantVariableNull - $reshook = $hookmanager->executeHooks('calcul_price_total', $parameters, $tmpobject, $tmpaction); + // @phan-suppress-next-line PhanPluginConstantVariableNull + $reshook = $hookmanager->executeHooks('calcul_price_total', $parameters, $tmpobject, $tmpaction); // @phan-suppress-current-line PhanPluginConstantVariableNull if ($reshook > 0 && !empty($hookmanager->resArray['result'])) { $result = $hookmanager->resArray['result']; } From 33d65c19dd2be9b70a73dbfb4dd32bfcb1a902b4 Mon Sep 17 00:00:00 2001 From: John BOTELLA <68917336+thersane-john@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:40:52 +0100 Subject: [PATCH 159/387] Fix css login page patch 02 (#36320) * fix css login page patch 01 * fix css login page patch 02 * fix css login page patch 02 --------- Co-authored-by: Laurent Destailleur --- htdocs/public/webportal/css/login.css | 2 +- htdocs/public/webportal/css/pico.css.php | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/htdocs/public/webportal/css/login.css b/htdocs/public/webportal/css/login.css index 2c862812779..5de421965c4 100644 --- a/htdocs/public/webportal/css/login.css +++ b/htdocs/public/webportal/css/login.css @@ -25,7 +25,7 @@ body.login-page { backdrop-filter: blur(10px) ; position: relative; - height: 600px; + min-height: 600px; width: 500px; overflow: hidden; box-shadow: 0px 0px 24px hsl(var(--primary-color-hue), var(--primary-color-saturation), 30%,0.4); diff --git a/htdocs/public/webportal/css/pico.css.php b/htdocs/public/webportal/css/pico.css.php index 8a19eb8c66c..608d3d8ba16 100644 --- a/htdocs/public/webportal/css/pico.css.php +++ b/htdocs/public/webportal/css/pico.css.php @@ -307,6 +307,7 @@ kbd { --muted-color: hsl(205, 10%, 50%); --muted-border-color: hsl(205, 20%, 94%); + --outline-button-background: var(--background-color); --banner-background : #ededed; --primary-color-hue : primaryColorHsl['h']; /* TODO : WHY ?? this is already in custom.css.php */ ?>; --primary-color-saturation : primaryColorHsl['s']; /* TODO : WHY ?? this is already in custom.css.php */ ?>%; @@ -1145,12 +1146,12 @@ input[type=reset]:focus { :is(button, input[type=submit], input[type=button], [role=button]).outline, input[type=reset].outline { - --background-color: transparent; + --background-color: var(--outline-button-background); --color: var(--primary); } :is(button, input[type=submit], input[type=button], [role=button]).outline:is([aria-current], :hover, :active, :focus), input[type=reset].outline:is([aria-current], :hover, :active, :focus) { - --background-color: transparent; + --background-color: var(--outline-button-background); --color: var(--primary-hover); } From fc8cc70d7ea41b850bc51e9c3fab6e5dcbaff676 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 19 Nov 2025 12:50:32 +0100 Subject: [PATCH 160/387] Clean code --- htdocs/core/class/commonobject.class.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index 29379743f84..f887b18b265 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -2158,20 +2158,21 @@ abstract class CommonObject public function fetch_origin() { // phpcs:enable - $origin = $this->origin ? $this->origin : $this->origin_type; + $tmpclassname = $this->origin ? $this->origin : $this->origin_type; // Manage classes with non standard name - if ($origin == 'shipping') { - $origin = 'expedition'; + if ($tmpclassname == 'shipping') { + $tmpclassname = 'Expedition'; } - if ($origin == 'delivery') { - $origin = 'livraison'; + if ($tmpclassname == 'delivery') { + $tmpclassname = 'Livraison'; } - if ($origin == 'order_supplier' || $origin == 'supplier_order') { - $origin = 'commandeFournisseur'; + if ($tmpclassname == 'order_supplier' || $tmpclassname == 'supplier_order') { + $tmpclassname = 'CommandeFournisseur'; } - $classname = ucfirst($origin); + $classname = ucfirst($tmpclassname); + $this->origin_object = new $classname($this->db); // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall $this->origin_object->fetch($this->origin_id); From c65e03ecb678405307e5c3c61d9fa4895e290c55 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 19 Nov 2025 13:01:35 +0100 Subject: [PATCH 161/387] Fix CSS --- htdocs/fourn/facture/card.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/fourn/facture/card.php b/htdocs/fourn/facture/card.php index 223153987ea..b17283a7de1 100644 --- a/htdocs/fourn/facture/card.php +++ b/htdocs/fourn/facture/card.php @@ -1156,7 +1156,7 @@ if (empty($reshook)) { $objectsrc->fetch_thirdparty(); if (!empty($object->origin_type) && !empty($object->origin_id)) { - $object->linkedObjectsIds[$object->origin_type] = $object->origin_id; + $object->linkedObjectsIds[$object->origin_type][0] = $object->origin_id; } // Add also link with order if object is reception @@ -1165,7 +1165,7 @@ if (empty($reshook)) { if (count($objectsrc->linkedObjectsIds['order_supplier']) > 0) { foreach ($objectsrc->linkedObjectsIds['order_supplier'] as $key => $value) { - $object->linkedObjectsIds['order_supplier'] = $value; + $object->linkedObjectsIds['order_supplier'][0] = $value; } } } From 292b2352415c23377a8aaae59168aba43ff01e71 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 19 Nov 2025 13:14:48 +0100 Subject: [PATCH 162/387] Fix CI --- htdocs/fourn/facture/card.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/fourn/facture/card.php b/htdocs/fourn/facture/card.php index b17283a7de1..c2488b8a1f7 100644 --- a/htdocs/fourn/facture/card.php +++ b/htdocs/fourn/facture/card.php @@ -1156,7 +1156,7 @@ if (empty($reshook)) { $objectsrc->fetch_thirdparty(); if (!empty($object->origin_type) && !empty($object->origin_id)) { - $object->linkedObjectsIds[$object->origin_type][0] = $object->origin_id; + $object->linkedObjectsIds[$object->origin_type][] = $object->origin_id; } // Add also link with order if object is reception @@ -1165,7 +1165,7 @@ if (empty($reshook)) { if (count($objectsrc->linkedObjectsIds['order_supplier']) > 0) { foreach ($objectsrc->linkedObjectsIds['order_supplier'] as $key => $value) { - $object->linkedObjectsIds['order_supplier'][0] = $value; + $object->linkedObjectsIds['order_supplier'][] = $value; } } } From c4f1dbddbb68ffe4bc146b7906c97275056f806a Mon Sep 17 00:00:00 2001 From: iouston <4319513+iouston@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:17:48 +0100 Subject: [PATCH 163/387] Add product type check in line validation (#36319) * Add product type check in line validation needed by sous total plugins style which use line with type 9 for title, subtitle or free text. without the chek inb line validation, title, sub title or free text disappears * Update card.php --------- Co-authored-by: Laurent Destailleur --- htdocs/fourn/commande/card.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/fourn/commande/card.php b/htdocs/fourn/commande/card.php index 22abd335611..11fdb0ef925 100644 --- a/htdocs/fourn/commande/card.php +++ b/htdocs/fourn/commande/card.php @@ -1374,7 +1374,7 @@ if (empty($reshook)) { $num = count($lines); for ($i = 0; $i < $num; $i++) { - if (empty($lines[$i]->subprice) || $lines[$i]->qty <= 0 || !in_array($lines[$i]->id, $selectedLines)) { + if ((empty($lines[$i]->subprice) || $lines[$i]->qty <= 0 || !in_array($lines[$i]->id, $selectedLines)) && $lines[$i]->product_type != 9) { continue; } From f7f0b408e24a92916b907ca62af56bf7481eada1 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 19 Nov 2025 14:06:07 +0100 Subject: [PATCH 164/387] Migrate holiday files --- htdocs/install/upgrade2.php | 91 +++++++++++++++++++++++++++++++++ htdocs/langs/en_US/install.lang | 1 + 2 files changed, 92 insertions(+) diff --git a/htdocs/install/upgrade2.php b/htdocs/install/upgrade2.php index b0260d313a4..60477dec9ce 100644 --- a/htdocs/install/upgrade2.php +++ b/htdocs/install/upgrade2.php @@ -659,6 +659,8 @@ if (!GETPOST('action', 'aZ09') || preg_match('/upgrade/i', GETPOST('action', 'aZ if (versioncompare($versiontoarray, $afterversionarray) >= 0 && versioncompare($versiontoarray, $beforeversionarray) <= 0) { dol_syslog("Run migrate_... versionto is between ".json_encode($afterversionarray)." and ".json_encode($beforeversionarray)); + migrate_holiday_path(); + migrate_apiresttokens(); migrate_blockedlog_add_hmac_key(); @@ -4772,6 +4774,95 @@ function migrate_user_photospath2() } + +/** + * Migrate file from old path to new one for users + * + * @return void + */ +function migrate_holiday_path() +{ + global $conf, $db, $langs, $user; + + print '
'; +} + + /* A faire egalement: Modif statut paye et fk_facture des factures payes completement On recherche facture incorrecte: diff --git a/htdocs/langs/en_US/install.lang b/htdocs/langs/en_US/install.lang index ae0ee9832c9..14b112ab90d 100644 --- a/htdocs/langs/en_US/install.lang +++ b/htdocs/langs/en_US/install.lang @@ -202,6 +202,7 @@ MigrationFieldsSocialNetworks=Migration of users fields social networks (%s) MigrationReloadModule=Reload module %s MigrationResetBlockedLog=Reset module BlockedLog for v7 algorithm MigrationImportOrExportProfiles=Migration of import or export profiles (%s) +MigrationHolidayPath=Migration of file paths for holidays ShowNotAvailableOptions=Show unavailable options HideNotAvailableOptions=Hide unavailable options ErrorFoundDuringMigration=Error(s) were reported during the migration process so next step is not available. To ignore errors, you can click here, but the application or some features may not work correctly until the errors are resolved. From 6d5835e90f55b2fd82901356741f81fee39f51ca Mon Sep 17 00:00:00 2001 From: Yamil Esteban Garcia <120027058+developmentOYR@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:09:05 +0100 Subject: [PATCH 165/387] FIX Move 'holiday' from old path array to new one (#36308) * Remove 'supplier_invoice' from old path array * Update module path in arrayforoldpath Sorry Eldy, I was confused. You are absolutely right, it is already corrected. --------- Co-authored-by: Laurent Destailleur --- htdocs/core/lib/functions.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 1df7a74e28c..010da81f27a 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -8687,7 +8687,7 @@ function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = ' $path = ''; // Define $arrayforoldpath that is module path using a hierarchy on more than 1 level. - $arrayforoldpath = array('cheque' => 2, 'category' => 2, 'holiday' => 2, 'supplier_invoice' => 2, 'invoice_supplier' => 2, 'mailing' => 2, 'supplier_payment' => 2); + $arrayforoldpath = array('cheque' => 2, 'category' => 2, 'supplier_invoice' => 2, 'invoice_supplier' => 2, 'mailing' => 2, 'supplier_payment' => 2); if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) { $arrayforoldpath['product'] = 2; } From 96e35c7a33a2a49e1b5dc887bf6113f4f1e932a7 Mon Sep 17 00:00:00 2001 From: MDW Date: Wed, 19 Nov 2025 14:16:00 +0100 Subject: [PATCH 166/387] Fix: PhanTypeMismatchProperty error in invoice creation (#36323) * :bug: Fix PhanTypeMismatchProperty error in invoice creation The error occurred due to incorrect type handling in the invoice creation process. The changes fix this by properly handling the subtype field as an integer. This ensures type consistency and prevents potential runtime errors. * FIX: Correct assignment to linkedObjectsIds in card.php # FIX: Correct assignment to linkedObjectsIds in card.php Modified the structure of linkedObjectsIds to include the rowid as a key to match the property type. --------- Co-authored-by: Laurent Destailleur --- dev/tools/phan/baseline.txt | 2 +- htdocs/core/class/commonobject.class.php | 8 +++--- htdocs/fourn/facture/card.php | 32 ++++++++++++------------ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/dev/tools/phan/baseline.txt b/dev/tools/phan/baseline.txt index e97b76bdf14..5b57fb43344 100644 --- a/dev/tools/phan/baseline.txt +++ b/dev/tools/phan/baseline.txt @@ -163,7 +163,7 @@ return [ 'htdocs/fourn/class/fournisseur.commande.class.php' => ['PhanUndeclaredProperty'], 'htdocs/fourn/commande/card.php' => ['PhanUndeclaredProperty'], 'htdocs/fourn/facture/card-rec.php' => ['PhanTypeMismatchArgument', 'PhanUndeclaredGlobalVariable', 'PhanUndeclaredProperty'], - 'htdocs/fourn/facture/card.php' => ['PhanTypeMismatchArgument', 'PhanTypeMismatchProperty'], + 'htdocs/fourn/facture/card.php' => ['PhanTypeMismatchArgument'], 'htdocs/fourn/facture/rapport.php' => ['PhanTypeMismatchArgument'], 'htdocs/fourn/facture/tpl/linkedobjectblock.tpl.php' => ['PhanUndeclaredProperty'], 'htdocs/holiday/card_group.php' => ['PhanTypeMismatchArgument'], diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index f887b18b265..1cd208e9c28 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -215,7 +215,7 @@ abstract class CommonObject public $linked_objects; /** - * @var int[][] Array of linked objects ids. Loaded by ->fetchObjectLinked + * @var array> Array of linked objects ids. Loaded by ->fetchObjectLinked */ public $linkedObjectsIds; @@ -805,7 +805,7 @@ abstract class CommonObject public $totalpaid; /** - * @var int|float|null Amount already paid from getSommePaiement(), like $totalpaid, but in the foreign currency + * @var int|float|null Amount already paid from getSommePaiement(), like `$totalpaid`, but in the foreign currency * @see $totalpaid, $alreadypaid */ public $totalpaid_multicurrency; @@ -1283,8 +1283,8 @@ abstract class CommonObject if ($this->restrictiononfksoc && property_exists($this, 'socid') && !empty($this->socid) && !$user->hasRight('societe', 'client', 'voir')) { $sql_allowed_contacts = 'SELECT COUNT(*) as cnt FROM '.$this->db->prefix().'societe_commerciaux as sc'; - $sql_allowed_contacts.= ' WHERE sc.fk_soc = '.(int) $this->socid; - $sql_allowed_contacts.= ' AND sc.fk_user = '.(int) $user->id; + $sql_allowed_contacts .= ' WHERE sc.fk_soc = '.(int) $this->socid; + $sql_allowed_contacts .= ' AND sc.fk_user = '.(int) $user->id; $resql_allowed_contacts = $this->db->query($sql_allowed_contacts); diff --git a/htdocs/fourn/facture/card.php b/htdocs/fourn/facture/card.php index c2488b8a1f7..151e765978e 100644 --- a/htdocs/fourn/facture/card.php +++ b/htdocs/fourn/facture/card.php @@ -914,10 +914,10 @@ if (empty($reshook)) { if (!$error) { $tmpproject = GETPOSTINT('projectid'); - // Creation facture + // Create Supplier Invoice $object->ref = GETPOST('ref', 'alphanohtml'); $object->ref_supplier = GETPOST('ref_supplier', 'alphanohtml'); - $object->subtype = GETPOST('subtype', 'alphanohtml'); + $object->subtype = GETPOSTINT('subtype'); $object->socid = GETPOSTINT('socid'); $object->label = GETPOST('label', 'alphanohtml'); $object->libelle = $object->label; // Deprecated @@ -1156,7 +1156,7 @@ if (empty($reshook)) { $objectsrc->fetch_thirdparty(); if (!empty($object->origin_type) && !empty($object->origin_id)) { - $object->linkedObjectsIds[$object->origin_type][] = $object->origin_id; + $object->linkedObjectsIds[$object->origin_type][-1] = $object->origin_id; } // Add also link with order if object is reception @@ -1165,7 +1165,7 @@ if (empty($reshook)) { if (count($objectsrc->linkedObjectsIds['order_supplier']) > 0) { foreach ($objectsrc->linkedObjectsIds['order_supplier'] as $key => $value) { - $object->linkedObjectsIds['order_supplier'][] = $value; + $object->linkedObjectsIds['order_supplier'][-1] = $value; } } } @@ -3864,7 +3864,7 @@ if ($action == 'create') { if ($object->type != FactureFournisseur::TYPE_CREDIT_NOTE) { // Total already paid - print ''; // Remainder to pay - print ''; //print ''; @@ -4035,13 +4035,13 @@ if ($action == 'create') { print ''; // Billed - print ''; + print ''; //print ''; print ''; print ''; // Remainder to pay back - print ''; + // print ''; } print ''; @@ -1774,18 +1781,20 @@ if ($action == 'create') { $url = dol_buildpath('comm/action/card.php', 2).$urloption; // update task list - print "\n".''."\n"; + ?> + + '; @@ -1931,39 +1940,37 @@ if ($action == 'create') { }); })'; print ''."\n"; - - print "\n".''."\n"; + ?> + + 0 && $action != 'create') { $url = DOL_URL_ROOT.'/comm/action/card.php'.$urloption; // update task list - print "\n".''."\n"; + ?> + + selectTasks((!empty($societe->id) ? $societe->id : -1), $object->elementid, 'fk_element', 24, 0, '', 1, 0, 0, 'maxwidth500', (string) $object->fk_project, 'all', null, 1); print ''; @@ -2372,18 +2381,20 @@ if ($id > 0 && $action != 'create') { print '
'; - $out .= ''; + // Status ($percent can be 'na'or < 100 or 100) + $out .= ''; + $out .= $formactions->form_select_status_action('formaction', $percent, 1, 'search_complete', 1, 2, 'search_status width100 onrightofpage', 1); $out .= ''; $out .= $actionstatic->getTypePicto(); - //if (empty($conf->dol_optimize_smallscreen)) { $out .= $labelOfTypeToShow; - //} $out .= '
'; + + print ''.$langs->trans('MigrationHolidayPath')."
\n"; + + include_once DOL_DOCUMENT_ROOT.'/holiday/class/holiday.class.php'; + $holiday = new Holiday($db); + + $sql = "SELECT rowid as uid, ref, entity from ".MAIN_DB_PREFIX."holiday"; // Get list of all holiday + $resql = $db->query($sql); + if ($resql) { + while ($obj = $db->fetch_object($resql)) { + //$holiday->fetch($obj->uid); + $holiday->id = $obj->uid; + $holiday->ref = $obj->ref; + $holiday->entity = $obj->entity; + + //echo '
'.$holiday->id.' -> '.$holiday->entity; + $entity = (empty($holiday->entity) ? 1 : $holiday->entity); + if ($entity > 1) { + $dir = DOL_DATA_ROOT.'/'.$entity.'/holiday'; + } else { + $dir = $conf->holiday->multidir_output[$entity]; // $conf->user->multidir_output[] for each entity is construct by the multicompany module + } + + if ($dir) { + //print "Process holiday id ".$holiday->id."
\n"; + $origin = $dir.'/'.get_exdir($holiday->id, 2, 0, 1, $holiday, 'holiday'); // Use old behaviour to get x/y path + $destin = $dir.'/'.$holiday->ref; + + $origin_osencoded = dol_osencode($origin); + + dol_mkdir($destin); + + //echo $origin.' -> '.$destin."
\n"; + if (dol_is_dir($origin)) { + $handle = opendir($origin_osencoded); + if (is_resource($handle)) { + while (($file = readdir($handle)) !== false) { + if ($file == '.' || $file == '..') { + continue; + } + + if (dol_is_dir($origin.'/'.$file)) { // it is a dir (like 'thumbs') + $thumbs = opendir($origin_osencoded.'/'.$file); + if (is_resource($thumbs)) { + dol_mkdir($destin.'/'.$file); + while (($thumb = readdir($thumbs)) !== false) { + if (!dol_is_file($destin.'/'.$file.'/'.$thumb)) { + if ($thumb == '.' || $thumb == '..') { + continue; + } + + //print $origin.'/'.$file.'/'.$thumb.' -> '.$destin.'/'.$file.'/'.$thumb.'
'."\n"; + print '.'; + dol_copy($origin.'/'.$file.'/'.$thumb, $destin.'/'.$file.'/'.$thumb, '0', 0); + //var_dump('aaa');exit; + } + } + // dol_delete_dir($origin.'/'.$file); + } + } else { // it is a file + if (!dol_is_file($destin.'/'.$file)) { + //print $origin.'/'.$file.' -> '.$destin.'/'.$file.'
'."\n"; + print '.'; + dol_copy($origin.'/'.$file, $destin.'/'.$file, '0', 0); + //var_dump('eee');exit; + } + } + } + } + } + } + } + } + + print '
'; + print '
'; print ''; if ($object->type != FactureFournisseur::TYPE_DEPOSIT) { print $langs->trans('AlreadyPaidNoCreditNotesNoDeposits'); @@ -3930,7 +3930,7 @@ if ($action == 'create') { // Pay partially 'escompte' if (($object->status == FactureFournisseur::STATUS_CLOSED || $object->status == FactureFournisseur::STATUS_ABANDONED) && $object->close_code == 'discount_vat') { - print '
'; + print '
'; print ''; print $form->textwithpicto($langs->trans("Discount"), $langs->trans("HelpEscompte"), - 1); print ''; @@ -3943,7 +3943,7 @@ if ($action == 'create') { } // Paye partiellement ou Abandon 'badsupplier' if (($object->status == FactureFournisseur::STATUS_CLOSED || $object->status == FactureFournisseur::STATUS_ABANDONED) && $object->close_code == 'badsupplier') { - print '
'; + print '
'; print ''; print $form->textwithpicto($langs->trans("Abandoned"), $langs->trans("HelpAbandonBadCustomer"), - 1); print ''; @@ -3956,7 +3956,7 @@ if ($action == 'create') { } // Paye partiellement ou Abandon 'product_returned' if (($object->status == FactureFournisseur::STATUS_CLOSED || $object->status == FactureFournisseur::STATUS_ABANDONED) && $object->close_code == 'product_returned') { - print '
'; + print '
'; print ''; print $form->textwithpicto($langs->trans("ProductReturned"), $langs->trans("HelpAbandonProductReturned"), - 1); print ''; @@ -3969,7 +3969,7 @@ if ($action == 'create') { } // Paye partiellement ou Abandon 'abandon' if (($object->status == FactureFournisseur::STATUS_CLOSED || $object->status == FactureFournisseur::STATUS_ABANDONED) && $object->close_code == 'abandon') { - print '
'; + print '
'; $text = $langs->trans("HelpAbandonOther"); if ($object->close_note) { $text .= '

'.$langs->trans("Reason").':'.$object->close_note; @@ -3987,7 +3987,7 @@ if ($action == 'create') { } // Billed - print '
'; + print '
'; print ''; print $langs->trans("Billed"); print ''; @@ -3997,7 +3997,7 @@ if ($action == 'create') { print '
'; + print '
'; print ''; print $langs->trans('RemainderToPay'); if ($resteapayeraffiche < 0) { @@ -4011,7 +4011,7 @@ if ($action == 'create') { // Remainder to pay Multicurrency if (isModEnabled('multicurrency') && (($object->multicurrency_code && $object->multicurrency_code != $conf->currency) || $object->multicurrency_tx != 1)) { - print '
'; + print '
'; print ''; print $langs->trans('RemainderToPayMulticurrency'); if ($resteapayeraffiche < 0) { @@ -4027,7 +4027,7 @@ if ($action == 'create') { $cssforamountpaymentcomplete = 'amountpaymentneutral'; // Total already paid back - print '
'; + print '
'; print $langs->trans('AlreadyPaidBack'); print '
'.$langs->trans("Billed").'
'.$langs->trans("Billed").''.price($sign * $object->total_ttc).'
'; + print '
'; print ''; print $langs->trans('RemainderToPayBack'); if ($resteapayeraffiche > 0) { @@ -4055,7 +4055,7 @@ if ($action == 'create') { // Remainder to pay back Multicurrency if (isModEnabled('multicurrency') && (($object->multicurrency_code && $object->multicurrency_code != $conf->currency) || $object->multicurrency_tx != 1)) { - print '
'; + print '
'; print ''; print $langs->trans('RemainderToPayBackMulticurrency'); if ($resteapayeraffiche > 0) { From 3cd79f449708fd3f9b397ca7f09d9824547303e3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:16:21 +0100 Subject: [PATCH 167/387] PHPStan > Update baseline (#36305) Co-authored-by: Dolibot --- dev/build/phpstan/phpstan-baseline.neon | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/dev/build/phpstan/phpstan-baseline.neon b/dev/build/phpstan/phpstan-baseline.neon index 9ed766a4c54..f298811936b 100644 --- a/dev/build/phpstan/phpstan-baseline.neon +++ b/dev/build/phpstan/phpstan-baseline.neon @@ -1314,12 +1314,6 @@ parameters: count: 1 path: ../../../htdocs/barcode/printsheet.php - - - message: '#^Variable \$contextpage in empty\(\) always exists and is not falsy\.$#' - identifier: empty.variable - count: 1 - path: ../../../htdocs/blockedlog/admin/blockedlog_list.php - - message: '#^Strict comparison using \=\=\= between ''facture'' and ''facture'' will always evaluate to true\.$#' identifier: identical.alwaysTrue @@ -8346,12 +8340,6 @@ parameters: count: 1 path: ../../../htdocs/core/tpl/document_actions_post_headers.tpl.php - - - message: '#^Variable \$modulepart might not be defined\.$#' - identifier: variable.undefined - count: 3 - path: ../../../htdocs/core/tpl/document_actions_post_headers.tpl.php - - message: '#^Variable \$permissiontoadd might not be defined\.$#' identifier: variable.undefined @@ -8370,12 +8358,6 @@ parameters: count: 1 path: ../../../htdocs/core/tpl/document_actions_post_headers.tpl.php - - - message: '#^Variable \$upload_dir might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: ../../../htdocs/core/tpl/document_actions_post_headers.tpl.php - - message: '#^Variable \$object might not be defined\.$#' identifier: variable.undefined From 7e2f7f163df6e6f807ad381324575e2f5c501458 Mon Sep 17 00:00:00 2001 From: Alexandre SPANGARO Date: Wed, 19 Nov 2025 14:43:27 +0100 Subject: [PATCH 168/387] Accountancy - Resolve some problem on new function "Discount in accountancy" (#36285) * FIX Accountancy - Discount wrong function & problem HTML injection * FIX Accountancy - Wrong base for already / not yet function - Piece_num is not enough strong et editable * FIX Accountancy - Discount - Use closing date rather than the invoice date * FIX * PHPPhan * Update accountingjournal.class.php * Update accountingjournal.class.php --------- Co-authored-by: Laurent Destailleur --- .../class/accountingjournal.class.php | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/htdocs/accountancy/class/accountingjournal.class.php b/htdocs/accountancy/class/accountingjournal.class.php index 8f79952eeb2..1e0ead82205 100644 --- a/htdocs/accountancy/class/accountingjournal.class.php +++ b/htdocs/accountancy/class/accountingjournal.class.php @@ -759,7 +759,7 @@ class AccountingJournal extends CommonObject } // Build SQL - Customer invoices closed by discount - $sql = "SELECT f.rowid, f.ref, f.datef, f.fk_soc, f.total_ttc"; + $sql = "SELECT f.rowid, f.ref, f.datef, f.date_closing, f.fk_soc, f.total_ttc"; $sql .= " FROM ".MAIN_DB_PREFIX."facture as f"; $sql .= " WHERE f.entity IN (".getEntity('invoice', 0).')'; // We don't share object for accountancy, we use source object sharing $sql .= " AND f.fk_statut > 0"; @@ -770,19 +770,21 @@ class AccountingJournal extends CommonObject } $sql .= " AND f.close_code = 'discount_vat'"; if ($date_start && $date_end) { - $sql .= " AND f.datef >= '".$this->db->idate($date_start)."' AND f.datef <= '".$this->db->idate($date_end)."'"; + $sql .= " AND f.date_closing >= '".$this->db->idate($date_start)."' AND f.date_closing <= '".$this->db->idate($date_end)."'"; } if (getDolGlobalString('ACCOUNTING_DATE_START_BINDING')) { - $sql .= " AND f.datef >= '".$this->db->idate(getDolGlobalInt('ACCOUNTING_DATE_START_BINDING'))."'"; + $sql .= " AND f.date_closing >= '".$this->db->idate(getDolGlobalInt('ACCOUNTING_DATE_START_BINDING'))."'"; } if ($in_bookkeeping == 'already') { $sql .= " AND EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."accounting_bookkeeping ab"; - $sql .= " WHERE ab.doc_type = 'customer_invoice' AND ab.fk_doc = f.rowid AND ab.piece_num LIKE 'OD-ESC-%')"; + $sql .= " WHERE ab.doc_type = 'customer_invoice' AND ab.fk_doc = f.rowid"; + $sql .= " AND ab.code_journal = '".$this->db->escape($this->code)."')"; } elseif ($in_bookkeeping == 'notyet') { $sql .= " AND NOT EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."accounting_bookkeeping ab"; - $sql .= " WHERE ab.doc_type = 'customer_invoice' AND ab.fk_doc = f.rowid AND ab.piece_num LIKE 'OD-ESC-%')"; + $sql .= " WHERE ab.doc_type = 'customer_invoice' AND ab.fk_doc = f.rowid"; + $sql .= " AND ab.code_journal = '".$this->db->escape($this->code)."')"; } - $sql .= " ORDER BY f.datef"; + $sql .= " ORDER BY f.date_closing"; dol_syslog(__METHOD__, LOG_DEBUG); $resql = $this->db->query($sql); @@ -825,7 +827,8 @@ class AccountingJournal extends CommonObject } $bookkeeping_static = new BookKeeping($this->db); - $label_discount = $bookkeeping_static->accountingLabelForOperation($customer_static->getNomUrl(1, 'customer'), $invoice_static->ref, $langs->trans('DiscountGranted')); + $thirdpartyname = (string) $customer_static->name; + $label_discount = $bookkeeping_static->accountingLabelForOperation($thirdpartyname, $invoice_static->ref, $langs->trans('DiscountGranted')); // Distribution including VAT by rate $ttcByRate = array(); @@ -853,7 +856,9 @@ class AccountingJournal extends CommonObject 'blocks' => array(), ); - $docdate = $this->db->jdate($obj->datef); + $closingdate = !empty($obj->date_closing) ? $obj->date_closing : $obj->datef; + + $docdate = $this->db->jdate($closingdate); $docdate_fmt = dol_print_date($docdate, 'day'); $sumTTC = 0.0; @@ -1063,7 +1068,7 @@ class AccountingJournal extends CommonObject } // SQL - Supplier invoices closed by discount - $sql = "SELECT ff.rowid, ff.ref, ff.datef, ff.fk_soc, ff.total_ttc"; + $sql = "SELECT ff.rowid, ff.ref, ff.datef, ff.date_closing, ff.fk_soc, ff.total_ttc"; $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as ff"; $sql .= " WHERE ff.entity IN (".getEntity('facture_fourn', 0).")"; // We don't share object for accountancy $sql .= " AND ff.fk_statut > 0"; @@ -1074,19 +1079,21 @@ class AccountingJournal extends CommonObject } $sql .= " AND ff.close_code = 'discount_vat'"; if ($date_start && $date_end) { - $sql .= " AND ff.datef >= '".$this->db->idate($date_start)."' AND ff.datef <= '".$this->db->idate($date_end)."'"; + $sql .= " AND ff.date_closing >= '".$this->db->idate($date_start)."' AND ff.date_closing <= '".$this->db->idate($date_end)."'"; } if (getDolGlobalString('ACCOUNTING_DATE_START_BINDING')) { - $sql .= " AND ff.datef >= '".$this->db->idate(getDolGlobalInt('ACCOUNTING_DATE_START_BINDING'))."'"; + $sql .= " AND ff.date_closing >= '".$this->db->idate(getDolGlobalInt('ACCOUNTING_DATE_START_BINDING'))."'"; } if ($in_bookkeeping == 'already') { $sql .= " AND EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."accounting_bookkeeping ab"; - $sql .= " WHERE ab.doc_type = 'supplier_invoice' AND ab.fk_doc = ff.rowid AND ab.piece_num LIKE 'OD-ESC-FRS-%')"; + $sql .= " WHERE ab.doc_type = 'supplier_invoice' AND ab.fk_doc = ff.rowid"; + $sql .= " AND ab.code_journal = '".$this->db->escape($this->code)."')"; } elseif ($in_bookkeeping == 'notyet') { $sql .= " AND NOT EXISTS (SELECT 1 FROM ".MAIN_DB_PREFIX."accounting_bookkeeping ab"; - $sql .= " WHERE ab.doc_type = 'supplier_invoice' AND ab.fk_doc = ff.rowid AND ab.piece_num LIKE 'OD-ESC-FRS-%')"; + $sql .= " WHERE ab.doc_type = 'supplier_invoice' AND ab.fk_doc = ff.rowid"; + $sql .= " AND ab.code_journal = '".$this->db->escape($this->code)."')"; } - $sql .= " ORDER BY ff.datef"; + $sql .= " ORDER BY ff.date_closing"; dol_syslog(__METHOD__, LOG_DEBUG); $resql = $this->db->query($sql); @@ -1129,7 +1136,8 @@ class AccountingJournal extends CommonObject } $bookkeeping_static = new BookKeeping($this->db); - $label_discount = $bookkeeping_static->accountingLabelForOperation($supplier_static->getNomUrl(1, 'supplier'), $invoicesupplier_static->ref, $langs->trans('DiscountReceived')); + $thirdpartyname = (string) $supplier_static->name; + $label_discount = $bookkeeping_static->accountingLabelForOperation($thirdpartyname, $invoicesupplier_static->ref, $langs->trans('DiscountReceived')); // Distribution including VAT by rate $ttcByRate = array(); @@ -1157,7 +1165,9 @@ class AccountingJournal extends CommonObject 'blocks' => array(), ); - $docdate = $this->db->jdate($obj->datef); + $closingdate = !empty($obj->date_closing) ? $obj->date_closing : $obj->datef; + + $docdate = $this->db->jdate($closingdate); $docdate_fmt = dol_print_date($docdate, 'day'); $sumTTC = 0.0; From b449edad3a0798b4241204f0af00b660286ae78f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20FRANCE?= Date: Wed, 19 Nov 2025 14:47:41 +0100 Subject: [PATCH 169/387] enhance reminders create (#36321) * enhance reminders create * enhance reminders create * enhance reminders create * enhance reminders create * enhance reminders create * clean js * clean js * clean js * clean js * clean js --------- Co-authored-by: Laurent Destailleur --- htdocs/comm/action/card.php | 234 +++++++++--------- .../action/class/actioncommreminder.class.php | 7 + htdocs/contact/agenda.php | 4 +- htdocs/core/class/html.formactions.class.php | 25 +- 4 files changed, 143 insertions(+), 127 deletions(-) diff --git a/htdocs/comm/action/card.php b/htdocs/comm/action/card.php index d36db4042b9..ac4ffb4160c 100644 --- a/htdocs/comm/action/card.php +++ b/htdocs/comm/action/card.php @@ -57,7 +57,7 @@ require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php'; require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php'; // Load translation files required by the page -$langs->loadLangs(array("companies", "other", "commercial", "bills", "orders", "agenda", "mails")); +$langs->loadLangs(["companies", "other", "commercial", "bills", "orders", "agenda", "mails"]); // Get Parameters $action = GETPOST('action', 'aZ09'); @@ -156,20 +156,32 @@ if (empty($action) && empty($object->id)) { } // Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context -$hookmanager->initHooks(array('actioncard', 'globalcard')); +$hookmanager->initHooks(['actioncard', 'globalcard']); $TRemindTypes = []; if (getDolGlobalString('AGENDA_REMINDER_BROWSER')) { - $TRemindTypes['browser'] = array('label' => $langs->trans('BrowserPush'), 'disabled' => (getDolGlobalString('AGENDA_REMINDER_BROWSER') ? 0 : 1)); + $TRemindTypes['browser'] = [ + 'label' => $langs->trans('BrowserPush'), + 'disabled' => (getDolGlobalString('AGENDA_REMINDER_BROWSER') ? 0 : 1), + 'type' => ActionCommReminder::TYPE_USER, + 'data-html' => img_picto('', 'globe', 'class="pictofixedwidth"') . $langs->trans('BrowserPush'), + ]; } if (getDolGlobalString('AGENDA_REMINDER_EMAIL')) { - $TRemindTypes['email'] = array('label' => $langs->trans('EMail'), 'disabled' => (getDolGlobalString('AGENDA_REMINDER_EMAIL') ? 0 : 1)); + $TRemindTypes['email'] = [ + 'label' => $langs->trans('EMail'), + 'disabled' => (getDolGlobalString('AGENDA_REMINDER_EMAIL') ? 0 : 1), + 'type' => ActionCommReminder::TYPE_USER, + 'data-html' => img_picto('', 'email', 'class="pictofixedwidth"') . $langs->trans('EMail'), + ]; } if (getDolGlobalString('AGENDA_REMINDER_SMS')) { $langs->load('sms'); $TRemindTypes['sms'] = [ 'label' => $langs->trans('Sms'), 'disabled' => (getDolGlobalString('MAIN_SMS_SENDMODE') ? 0 : 1), + 'type' => ActionCommReminder::TYPE_USER, + 'data-html' => img_picto('', 'phoning_mobile', 'class="pictofixedwidth"') . $langs->trans('Sms'), ]; } $TDurationTypes = $form->getDurationTypes($langs); @@ -1551,30 +1563,24 @@ if ($action == 'create') { print $langs->trans("Until")." "; print $form->selectDate($repeateventlimitdate, 'limit', 0, 0, 0, "action", 1, 0, 0, '', '', '', '', 1, '', '', 'tzuserrel'); print ''; - - print ''; + + '; - //print '
'; // update task list - print "\n".''."\n"; + }); + + 0) { @@ -2409,7 +2420,7 @@ if ($id > 0 && $action != 'create') { // Description print '
'.$langs->trans("Description").''; - // Editeur wysiwyg + // Wysiwyg editor require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php'; $doleditor = new DolEditor('note', $object->note_private, '', 120, 'dolibarr_notes', 'In', true, true, isModEnabled('fckeditor'), ROWS_4, '90%'); $doleditor->Create(); @@ -2487,28 +2498,27 @@ if ($id > 0 && $action != 'create') { } print '
'; - - print "\n".''."\n"; + ?> + + displayCanvasExists($action)) { $titlelist = $langs->trans("Actions").(is_numeric($nbEvent) ? '('.$nbEvent.')' : ''); } - $morehtmlright = ''; - - print_barre_liste($titlelist, 0, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', 0, -1, '', 0, $morehtmlright, '', 0, 1, 0); + print_barre_liste($titlelist, 0, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', 0, -1, '', 0, $newcardbutton, '', 0, 1, 0); // List of all actions $filters = array(); diff --git a/htdocs/core/class/html.formactions.class.php b/htdocs/core/class/html.formactions.class.php index 52a9adcaa44..37dbe914059 100644 --- a/htdocs/core/class/html.formactions.class.php +++ b/htdocs/core/class/html.formactions.class.php @@ -2,7 +2,7 @@ /* Copyright (c) 2008-2012 Laurent Destailleur * Copyright (C) 2010-2012 Regis Houssin * Copyright (C) 2010-2018 Juanjo Menent - * Copyright (C) 2024 Frédéric France + * Copyright (C) 2024-2025 Frédéric France * Copyright (C) 2024 MDW * * This program is free software; you can redistribute it and/or modify @@ -57,27 +57,28 @@ class FormActions /** * Show list of action status * - * @param string $formname Name of form where select is included - * @param string $selected Preselected value (-1..100) - * @param int $canedit 1=can edit, 0=read only - * @param string $htmlname Name of html prefix for html fields (selectX and valX) - * @param integer $showempty Show an empty line if select is used - * @param integer $onlyselect 0=Standard, 1=Hide percent of completion and force usage of a select list, 2=Same than 1 and add "Incomplete (Todo+Running) - * @param string $morecss More css on select field - * @param int $nooutput 1=No output, return string. 0=Print on output - * @return string|void + * @param string $formname Name of form where select is included + * @param string $selected Preselected value (-1..100) + * @param int<0,1> $canedit 1=can edit, 0=read only + * @param string $htmlname Name of html prefix for html fields (selectX and valX) + * @param integer $showempty Show an empty line if select is used + * @param integer $onlyselect 0=Standard, 1=Hide percent of completion and force usage of a select list, 2=Same than 1 and add "Incomplete (Todo+Running) + * @param string $morecss More css on select field + * @param int<0,1> $nooutput 1=No output, return string. 0=Print on output + * @return void|string + * @phpstan-return ($nooutput is 1 ? void : string) */ public function form_select_status_action($formname, $selected, $canedit = 1, $htmlname = 'complete', $showempty = 0, $onlyselect = 0, $morecss = 'maxwidth100', $nooutput = 0) { // phpcs:enable global $langs, $conf; - $listofstatus = array( + $listofstatus = [ 'na' => $langs->trans("ActionNotApplicable"), '0' => $langs->trans("ActionsToDoShort"), '50' => $langs->trans("ActionRunningShort"), '100' => $langs->trans("ActionDoneShort") - ); + ]; // +ActionUncomplete $out = ''; From eb2d34533030167a2e81e0cee4725c4f6f19a63c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnter=20Lukas?= Date: Wed, 19 Nov 2025 14:49:16 +0100 Subject: [PATCH 170/387] Fix #36282: Update shipment reference handling in commondocgenerator (#36283) * Update shipment reference handling in commondocgenerator * Refactor order reference assignment for clarity * Refactor origin object handling in shipment array * Refactor origin object handling in shipment array --------- Co-authored-by: Laurent Destailleur --- htdocs/core/class/commondocgenerator.class.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/htdocs/core/class/commondocgenerator.class.php b/htdocs/core/class/commondocgenerator.class.php index 0b2c9c07515..fb265066c03 100644 --- a/htdocs/core/class/commondocgenerator.class.php +++ b/htdocs/core/class/commondocgenerator.class.php @@ -1080,10 +1080,12 @@ abstract class CommonDocGenerator $array_shipment = $this->fill_substitutionarray_with_extrafields($object, $array_shipment, $extrafields, $array_key, $outputlangs); } - // Add info from $object->xxx where xxx has been loaded by fetch_origin() of shipment - if (is_object($object->commande) && !empty($object->commande->ref)) { - $array_shipment['order_ref'] = $object->commande->ref; - $array_shipment['order_ref_customer'] = $object->commande->ref_customer; + // Add info from $object->origin_object which has been loaded by fetch() of shipment + if ($object->origin_type == 'commande' && is_object($object->origin_object) && !empty($object->origin_object->ref)) { + $originOrder = $object->origin_object; + '@phan-var-force Commande $originOrder'; + $array_shipment['order_ref'] = $originOrder->ref; + $array_shipment['order_ref_customer'] = $originOrder->ref_customer; } // Load dim data From da77a932055fc4689d2c5a3b68e8093e05e793ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charl=C3=A8ne=20Benke?= <1179011+defrance@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:55:37 +0100 Subject: [PATCH 171/387] extrafields type link fail if object not internal (#36315) without send the classpath we have an error on selectforforms function Co-authored-by: Laurent Destailleur --- htdocs/core/class/extrafields.class.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/core/class/extrafields.class.php b/htdocs/core/class/extrafields.class.php index 823820faee1..fe85a429379 100644 --- a/htdocs/core/class/extrafields.class.php +++ b/htdocs/core/class/extrafields.class.php @@ -6,7 +6,7 @@ * Copyright (C) 2009-2012 Laurent Destailleur * Copyright (C) 2009-2012 Regis Houssin * Copyright (C) 2013 Florian Henry - * Copyright (C) 2015-2023 Charlene BENKE + * Copyright (C) 2015-2025 Charlene BENKE * Copyright (C) 2016 Raphaël Doursenaud * Copyright (C) 2017 Nicolas ZABOURI * Copyright (C) 2018-2025 Frédéric France @@ -1987,8 +1987,8 @@ class ExtraFields $element = 'project'; } - //$objectdesc = $param_list[0]; // Example: 'ObjectName:classPath:1:(status:=:1)' Replaced by next line: old line propagated also the filter to ajax call that was blocked by some WAF - $objectdesc = $tmparray[0]; // Example: 'ObjectName:classPath' To not propagate any filter (selectForForms do ajax call and propagating SQL filter is blocked by some WAF). Also we should use the filter into the definition in the ->fields of $elem if found. + //$objectdesc = $param_list[0]; // Example: 'ObjectName:classPath:1:(status:=:1)' Replaced by next line: old line propagated also the filter to ajax call that was blocked by some WAF + $objectdesc = $tmparray[0].(empty($tmparray[1]) ? "" : ":".$tmparray[1]); // Example: 'ObjectName:classPath' To not propagate any filter (selectForForms do ajax call and propagating SQL filter is blocked by some WAF). Also we should use the filter into the definition in the ->fields of $elem if found. $objectfield = $element.':options_'.$key; // Example: 'actioncomm:options_fff' To be used in priority to know object linked with all its definition (including filters) $out = $form->selectForForms($objectdesc, $keyprefix.$key.$keysuffix, $value, $showempty, '', '', $morecss, '', 0, 0, '', $objectfield); From 548bb94e4f902f1ca4c976834449b7c5a0a73df0 Mon Sep 17 00:00:00 2001 From: kkhelifa-opendsi Date: Wed, 19 Nov 2025 15:14:53 +0100 Subject: [PATCH 172/387] NEW: Rework of the management of the card and fields on the web portal (#36076) * NEW: Rework of the management of the card and fields on the web portal * Correction pre-commit check * Correction affichage logo login * Ajout hook * Ajout params fonction FormWebPortal::convertAllLink() * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction travis * Correction travis * Correction travis * Correction travis * Correction * Fix get options of sellist by AJAX in webportal scope * Correction pre-commit * Correction pre-commit * Add hook and change hook name for viewImage controller * Correction phan * Corrections * Corrections --- htdocs/core/ajax/ajaxfield.php | 150 ++ htdocs/core/class/fieldinfos.class.php | 325 ++++ .../core/class/fields/booleanfield.class.php | 243 +++ .../core/class/fields/checkboxfield.class.php | 271 ++++ .../core/class/fields/chkbxlstfield.class.php | 288 ++++ .../core/class/fields/commonfield.class.php | 371 +++++ .../class/fields/commongeofield.class.php | 195 +++ .../class/fields/commonselectfield.class.php | 96 ++ .../class/fields/commonsellistfield.class.php | 373 +++++ htdocs/core/class/fields/datefield.class.php | 261 ++++ .../core/class/fields/datetimefield.class.php | 258 ++++ .../class/fields/datetimegmtfield.class.php | 258 ++++ .../core/class/fields/doublefield.class.php | 220 +++ .../core/class/fields/durationfield.class.php | 207 +++ htdocs/core/class/fields/emailfield.class.php | 217 +++ htdocs/core/class/fields/htmlfield.class.php | 217 +++ htdocs/core/class/fields/iconfield.class.php | 211 +++ htdocs/core/class/fields/intfield.class.php | 218 +++ htdocs/core/class/fields/ipfield.class.php | 222 +++ .../core/class/fields/linestrgfield.class.php | 32 + htdocs/core/class/fields/linkfield.class.php | 437 ++++++ .../core/class/fields/multiptsfield.class.php | 32 + .../core/class/fields/passwordfield.class.php | 249 ++++ htdocs/core/class/fields/phonefield.class.php | 217 +++ htdocs/core/class/fields/pointfield.class.php | 32 + .../core/class/fields/polygonfield.class.php | 32 + .../core/class/fields/pricecyfield.class.php | 316 ++++ htdocs/core/class/fields/pricefield.class.php | 224 +++ htdocs/core/class/fields/radiofield.class.php | 276 ++++ htdocs/core/class/fields/realfield.class.php | 220 +++ .../core/class/fields/selectfield.class.php | 271 ++++ .../core/class/fields/sellistfield.class.php | 298 ++++ htdocs/core/class/fields/starsfield.class.php | 242 +++ htdocs/core/class/fields/textfield.class.php | 229 +++ .../class/fields/timestampfield.class.php | 258 ++++ htdocs/core/class/fields/urlfield.class.php | 217 +++ .../core/class/fields/varcharfield.class.php | 213 +++ htdocs/core/class/fieldsmanager.class.php | 1309 +++++++++++++++++ htdocs/core/class/html.form.class.php | 413 ++++++ .../modules/barcode/modules_barcode.class.php | 15 + htdocs/langs/en_US/errors.lang | 3 + htdocs/langs/en_US/main.lang | 1 + htdocs/langs/fr_FR/errors.lang | 3 + htdocs/langs/fr_FR/main.lang | 1 + htdocs/public/webportal/ajax/ajaxfield.php | 153 ++ htdocs/public/webportal/img/login_logo.svg | 209 +++ htdocs/public/webportal/img/nophoto.png | Bin 0 -> 5278 bytes .../tpl/card-edit-actions-buttons.tpl.php | 28 + .../webportal/tpl/card-edit-footer.tpl.php | 26 + .../webportal/tpl/card-edit-header.tpl.php | 31 + .../webportal/tpl/card-edit-lines.tpl.php | 31 + .../tpl/card-edit-properties.tpl.php | 83 ++ htdocs/public/webportal/tpl/card-edit.tpl.php | 46 + .../tpl/card-view-actions-buttons.tpl.php | 42 + .../webportal/tpl/card-view-footer.tpl.php | 26 + .../webportal/tpl/card-view-header.tpl.php | 98 ++ .../webportal/tpl/card-view-lines.tpl.php | 29 + .../tpl/card-view-properties.tpl.php | 79 + htdocs/public/webportal/tpl/card-view.tpl.php | 49 + htdocs/public/webportal/tpl/footer.tpl.php | 156 +- htdocs/public/webportal/tpl/header.tpl.php | 4 + .../public/webportal/webportal.main.inc.php | 1 + htdocs/webportal/class/context.class.php | 12 +- .../class/html.formcardwebportal.class.php | 668 +-------- .../class/html.formwebportal.class.php | 534 ++++++- .../webportal/class/webPortalTheme.class.php | 34 +- .../class/webportalfieldsmanager.class.php | 57 + .../webportal/class/webportalmember.class.php | 12 +- .../abstractcard.controller.class.php | 76 + .../controllers/document.controller.class.php | 35 +- .../membercard.controller.class.php | 35 +- .../partnershipcard.controller.class.php | 36 +- .../viewimage.controller.class.php | 473 ++++++ test/phpunit/CodingPhpTest.php | 6 +- 74 files changed, 12491 insertions(+), 719 deletions(-) create mode 100644 htdocs/core/ajax/ajaxfield.php create mode 100644 htdocs/core/class/fieldinfos.class.php create mode 100644 htdocs/core/class/fields/booleanfield.class.php create mode 100644 htdocs/core/class/fields/checkboxfield.class.php create mode 100644 htdocs/core/class/fields/chkbxlstfield.class.php create mode 100644 htdocs/core/class/fields/commonfield.class.php create mode 100644 htdocs/core/class/fields/commongeofield.class.php create mode 100644 htdocs/core/class/fields/commonselectfield.class.php create mode 100644 htdocs/core/class/fields/commonsellistfield.class.php create mode 100644 htdocs/core/class/fields/datefield.class.php create mode 100644 htdocs/core/class/fields/datetimefield.class.php create mode 100644 htdocs/core/class/fields/datetimegmtfield.class.php create mode 100644 htdocs/core/class/fields/doublefield.class.php create mode 100644 htdocs/core/class/fields/durationfield.class.php create mode 100644 htdocs/core/class/fields/emailfield.class.php create mode 100644 htdocs/core/class/fields/htmlfield.class.php create mode 100644 htdocs/core/class/fields/iconfield.class.php create mode 100644 htdocs/core/class/fields/intfield.class.php create mode 100644 htdocs/core/class/fields/ipfield.class.php create mode 100644 htdocs/core/class/fields/linestrgfield.class.php create mode 100644 htdocs/core/class/fields/linkfield.class.php create mode 100644 htdocs/core/class/fields/multiptsfield.class.php create mode 100644 htdocs/core/class/fields/passwordfield.class.php create mode 100644 htdocs/core/class/fields/phonefield.class.php create mode 100644 htdocs/core/class/fields/pointfield.class.php create mode 100644 htdocs/core/class/fields/polygonfield.class.php create mode 100644 htdocs/core/class/fields/pricecyfield.class.php create mode 100644 htdocs/core/class/fields/pricefield.class.php create mode 100644 htdocs/core/class/fields/radiofield.class.php create mode 100644 htdocs/core/class/fields/realfield.class.php create mode 100644 htdocs/core/class/fields/selectfield.class.php create mode 100644 htdocs/core/class/fields/sellistfield.class.php create mode 100644 htdocs/core/class/fields/starsfield.class.php create mode 100644 htdocs/core/class/fields/textfield.class.php create mode 100644 htdocs/core/class/fields/timestampfield.class.php create mode 100644 htdocs/core/class/fields/urlfield.class.php create mode 100644 htdocs/core/class/fields/varcharfield.class.php create mode 100644 htdocs/core/class/fieldsmanager.class.php create mode 100644 htdocs/public/webportal/ajax/ajaxfield.php create mode 100644 htdocs/public/webportal/img/login_logo.svg create mode 100755 htdocs/public/webportal/img/nophoto.png create mode 100644 htdocs/public/webportal/tpl/card-edit-actions-buttons.tpl.php create mode 100644 htdocs/public/webportal/tpl/card-edit-footer.tpl.php create mode 100644 htdocs/public/webportal/tpl/card-edit-header.tpl.php create mode 100644 htdocs/public/webportal/tpl/card-edit-lines.tpl.php create mode 100644 htdocs/public/webportal/tpl/card-edit-properties.tpl.php create mode 100644 htdocs/public/webportal/tpl/card-edit.tpl.php create mode 100644 htdocs/public/webportal/tpl/card-view-actions-buttons.tpl.php create mode 100644 htdocs/public/webportal/tpl/card-view-footer.tpl.php create mode 100644 htdocs/public/webportal/tpl/card-view-header.tpl.php create mode 100644 htdocs/public/webportal/tpl/card-view-lines.tpl.php create mode 100644 htdocs/public/webportal/tpl/card-view-properties.tpl.php create mode 100644 htdocs/public/webportal/tpl/card-view.tpl.php create mode 100644 htdocs/webportal/class/webportalfieldsmanager.class.php create mode 100644 htdocs/webportal/controllers/abstractcard.controller.class.php create mode 100644 htdocs/webportal/controllers/viewimage.controller.class.php diff --git a/htdocs/core/ajax/ajaxfield.php b/htdocs/core/ajax/ajaxfield.php new file mode 100644 index 00000000000..8eb40de4a42 --- /dev/null +++ b/htdocs/core/ajax/ajaxfield.php @@ -0,0 +1,150 @@ + + * 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/ajaxfield.php + * \ingroup core + * \brief This script returns content of extrafield. See extrafield to update value. + */ + +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'); +} + +require '../../main.inc.php'; +require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php'; +require_once DOL_DOCUMENT_ROOT . '/core/class/fieldsmanager.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 = GETPOST('mode', 'aZ09'); +$value = GETPOST('value', 'alphanohtml'); +$dependencyvalue = GETPOST('dependencyvalue', 'alphanohtml'); +$limit = 10; +$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, + ] +]; +$nbResult = 0; +if ($object instanceof CommonObject) { + $extrafields = new ExtraFields($db); + $extrafields->fetch_name_optionals_label($object->table_element); + + $fieldManager = new FieldsManager($db); + $fieldInfos = $fieldManager->getFieldsInfos($objectkey, $object, $extrafields, $mode); + $field = $fieldManager->getFieldClass($fieldInfos->type); + if (isset($field)) { + if (method_exists($field, 'getOptions')) { + $fieldInfos->optionsSqlPage = $page; + $fieldInfos->optionsSqlLimit = $limit; + if ($dependencyvalue !== '') { + $fieldInfos->optionsSqlDependencyValue = $dependencyvalue; + } + + /** + * @var CommonSellistField $field + */ + '@phan-var-force CommonSellistField $field'; + $options = $field->getOptions($fieldInfos, $objectkey, $page == 1, true); + if (is_array($options)) { + $nbResult = count($options); + foreach ($options as $key => $option) { + $data['results'][] = [ + 'id' => $key, + 'text' => $option['label'], + ]; + } + } else { + dol_syslog('htdocs/core/ajax/ajaxfield.php ' . $field->errorsToString(), LOG_ERR); + } + } else { + dol_syslog('htdocs/core/ajax/ajaxfield.php method getOptions() don\'t exist in class ' . get_class($field), LOG_ERR); + } + } else { + dol_syslog('htdocs/core/ajax/ajaxfield.php ' . $fieldManager->errorsToString(), LOG_ERR); + } +} + +if ($page > 1 && $nbResult < 10) { + $data['pagination'] = [ + 'more' => false, + ]; +} +print json_encode($data); + +$db->close(); diff --git a/htdocs/core/class/fieldinfos.class.php b/htdocs/core/class/fieldinfos.class.php new file mode 100644 index 00000000000..86b9369138e --- /dev/null +++ b/htdocs/core/class/fieldinfos.class.php @@ -0,0 +1,325 @@ + + * 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/fieldinfos.class.php + * \ingroup core + * \brief File of class to stock field infos + */ + + +/** + * Class to stock field infos + */ +class FieldInfos +{ + /** + * @var CommonObject|null Object handler (by reference) + */ + public $object = null; + + /** + * @var string Display mode ('create', 'edit', 'view', 'list') + */ + public $mode = ''; + + /** + * @var int Type origin (object or extra field) + */ + public $fieldType = self::FIELD_TYPE_OBJECT; + + /** + * @var string Key name of the field + */ + public $key = ''; + + /** + * @var string Field origin type + * 'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]', + * 'select' (list of values are in 'options'. for integer list of values are in 'arrayofkeyval'), + * 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:SortField]]]]]]', + * 'chkbxlst:...', + * 'varchar(x)', + * 'text', 'text:none', 'html', + * 'double(24,8)', 'real', 'price', 'stock', + * 'date', 'datetime', 'timestamp', 'duration', + * 'boolean', 'checkbox', 'radio', 'array', + * 'email', 'phone', 'url', 'password', 'ip' + * Note: Filter must be a Dolibarr Universal Filter syntax string. Example: "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.status:!=:0) or (t.nature:is:NULL)" + */ + public $originType = ''; + + /** + * @var string Field type (The type name. ex: int, varchar, sellist, boolean, ... ) Used as class name, each type have this class. Ex: IntField, ... + */ + public $type = ''; + + /** + * @var string|null Name of the field in the class + */ + public $nameInClass = null; + + /** + * @var string|null Name of the field in the table + */ + public $nameInTable = null; + + /** + * @var string Field label (the translation key) + */ + public $label = ''; + + /** + * @var string Language file to load + */ + public $langFile = ''; + + /** + * @var string Field picto (code of a picto to show before value in forms) + */ + public $picto = ''; + + /** + * @var int Field position + */ + public $position = 0; + + /** + * @var bool Field required + */ + public $required = false; + + /** + * @var bool Field visible + */ + public $visible = true; + + /** + * @var bool Field visible in the header + */ + public $showOnHeader = false; + + /** + * @var bool Field editable + */ + public $editable = true; + + /** + * @var bool|null Field always editable + */ + public $alwaysEditable = null; + + /** + * @var string Field default value (to be converted in function of the type of the field) + */ + public $defaultValue = ''; + + /** + * @var int|null Field string min length + */ + public $minLength = null; + + /** + * @var int|null Field string max length + */ + public $maxLength = null; + + /** + * @var double|null Field numeric min value + */ + public $minValue = null; + + /** + * @var double|null Field numeric max value + */ + public $maxValue = null; + + /** + * @var string Field size (Example: 255, '24,8') + */ + public $size = ''; + + /** + * @var array Field options (for select, sellist, ...) + */ + public $options = array(); + + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(); + + /** + * @var string Specific check for GETPOST() when get field value (for type 'text' and 'html') + */ + public $getPostCheck = ''; + + /** + * @var string CSS for the input field + */ + public $css = ''; + + /** + * @var string CSS for the td + */ + public $tdCss = ''; + + /** + * @var string CSS for the input field in view mode + */ + public $viewCss = ''; + + /** + * @var string CSS for the input field in list + */ + public $listCss = ''; + + /** + * @var string Input placeholder + */ + public $inputPlaceholder = ''; + + /** + * @var string Field help + */ + public $help = ''; + + /** + * @var string Field comment. Is not used. You can store here any text of your choice. It is not used by application. + */ + public $comment = ''; + + /** + * @var string Prompt for AI + */ + public $aiPrompt = ''; + + /** + * @var bool If value of the field must be visible into the label of the combobox that list record + */ + public $showOnComboBox = false; + + /** + * @var bool If displayed in documents + */ + public $printable = false; + + /** + * @var bool If field set to null on clone + */ + public $emptyOnClone = false; + + /** + * @var bool If value must be unique + */ + public $unique = false; + + /** + * @var string If value is computed. (eval the provided string) + */ + public $computed = ''; + + /** + * @var bool Field must be validated + */ + public $validateField = false; + + /** + * @var int Is 1 or 2 to allow to add a picto to copy value into clipboard (1=picto after label, 2=picto after value) + */ + public $copyToClipboard = 0; + + /** + * @var bool Disable the input + */ + public $inputDisabled = false; + + /** + * @var bool Autofocus on the input + */ + public $inputAutofocus = false; + + /** + * @var bool Multi input on text type + */ + public $multiInput = false; + + /** + * @var string Field help in list + */ + public $listHelp = ''; + + /** + * @var bool Add total in list footer + */ + public $listTotalizable = false; + + /** + * @var bool Field checked in the list + */ + public $listChecked = true; + + /** + * @var string|null Alias table used for sql request (ex 't.') + */ + public $sqlAlias = null; + + /** + * @var string|null Dependency value (used for filter list from ajax) + */ + public $optionsSqlDependencyValue = null; + + /** + * @var int|null Current page when get sql result for options on 'sellist' and 'chkbxlst' field type + */ + public $optionsSqlPage = null; + + /** + * @var int|null Current offset when get sql result for options on 'sellist' and 'chkbxlst' field type + */ + public $optionsSqlOffset = null; + + /** + * @var int|null Current limit when get sql result for options on 'sellist' and 'chkbxlst' field type + */ + public $optionsSqlLimit = null; + + /** + * @var string|null getNameUrl() parameters 'xxx:xxx:xxx:...' + */ + public $getNameUrlParams = null; + + /** + * @var array Other parameters + */ + public $otherParams = array(); + + + const FIELD_TYPE_OBJECT = 0; + const FIELD_TYPE_EXTRA_FIELD = 1; +} diff --git a/htdocs/core/class/fields/booleanfield.class.php b/htdocs/core/class/fields/booleanfield.class.php new file mode 100644 index 00000000000..fb768b1703a --- /dev/null +++ b/htdocs/core/class/fields/booleanfield.class.php @@ -0,0 +1,243 @@ + + * + * 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/fields/booleanfield.class.php + * \ingroup core + * \brief File of class to boolean field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to boolean field + */ +class BooleanField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array('', '-1', -1); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->selectyesno($htmlName, $value, 1, false, 1, 1, 'width75 yesno'); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $checked = empty($value) ? '' : ' checked="checked"'; + $htmlName = $keyPrefix . $key . $keySuffix; + + $out = self::$form->inputType('checkbox', $htmlName, '1', $htmlName, $moreCss, $moreAttrib . $checked); + $out .= self::$form->inputType('hidden', $htmlName . '_boolean', '1'); // A hidden field ending with "_boolean" that is always set to 1. + + return $out; + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $checked = empty($value) ? '' : ' checked="checked"'; + $value = self::$form->inputType('checkbox', '', '1', '', $moreCss, $checked . $moreAttrib . ' readonly disabled'); + } else { + $value = yn($value ? 1 : 0); + } + + return $value; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + if (empty($moreCss)) $moreCss = $defaultCss; + $moreCss = trim((string) $moreCss); + + return empty($moreCss) ? '' : ' ' . $moreCss; + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isBool($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + // We test on a hidden field named "..._boolean" that is always set to 1 if param is in form so + // when nothing is provided we can make a difference between noparam in the form and param was set to nothing. + if (!GETPOSTISSET($htmlName . "_boolean")) { + $value = $defaultValue; + } elseif (GETPOSTISSET($htmlName)) { + $value = GETPOSTINT($htmlName) == 1 ? 1 : 0; + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOSTINT($htmlName); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + + $sql = " AND (" . $field . " = '" . $this->db->escape($value) . "'"; + if ($value == '0') { + $sql .= " OR " . $field . " IS NULL"; + } + $sql .= ")"; + + return $sql; + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/checkboxfield.class.php b/htdocs/core/class/fields/checkboxfield.class.php new file mode 100644 index 00000000000..ea58047ddad --- /dev/null +++ b/htdocs/core/class/fields/checkboxfield.class.php @@ -0,0 +1,271 @@ + + * + * 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/fields/checkboxfield.class.php + * \ingroup core + * \brief File of class to checkbox field(multiselect) + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonselectfield.class.php'; + + +/** + * Class to checkbox field (multiselect) + */ +class CheckboxField extends CommonSelectField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(array(), ''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return $this->printInputField($fieldInfos, $key, $value, $keyPrefix, $keySuffix, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + $values = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $optionsList = array(); + $options = $this->getOptions($fieldInfos, $key); + + return self::$form->multiselectarray($htmlName, $options, $values, 0, 0, $moreCss, 0, 0, $moreAttrib, '', '', (int) (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2'))); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 $langs; + $values = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $out = ''; + if (!$this->isEmptyValue($fieldInfos, $values)) { + $options = $this->getOptions($fieldInfos, $key); + $toPrint = array(); + foreach ($values as $val) { + $valueToPrint = null; + foreach ($options as $optionKey => $optionInfos) { + if (((string) $optionKey) == $val) { + $valueToPrint = $optionInfos['label']; + break; + } + } + if (!isset($valueToPrint)) { + $langs->load("errors"); + $valueToPrint = $langs->trans('ErrorRecordNotFound') . ' ( ' . $val . ' )'; + } + $toPrint[] = $valueToPrint; + } + $out = self::$form->outputMultiValues($toPrint); + } + + return $out; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth400'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + global $langs; + $values = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $result = parent::verifyFieldValue($fieldInfos, $key, $values); + if ($result && !$this->isEmptyValue($fieldInfos, $values)) { + $options = $this->getOptions($fieldInfos, $key); + foreach ($values as $val) { + $newVal = trim((string) $val); + if (!isset($options[$newVal])) { + self::$validator->error = $langs->trans('RequireValidValue'); + return false; + } + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $values = GETPOST($htmlName, 'array'); + + return $this->verifyFieldValue($fieldInfos, $key, $values); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $values = GETPOST($htmlName, 'array'); + if (is_array($values)) $values = implode(',', $values); + } else { + $values = $defaultValue; + } + + return $values; + } + + /** + * 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $values = GETPOST($htmlName, 'array'); + } else { + $values = $defaultValue; + } + + return $values; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value) && is_array($value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + + $tmp = "'" . implode("','", array_map(array($this->db, 'escape'), $value)) . "'"; + return " AND " . $field . " IN (" . $this->db->sanitize($tmp, 1) . ")"; + } + + return ''; + } + + /** + * Get list of options + * + * @param FieldInfos $fieldInfos Array of properties for field to show + * @param string $key Key of field + * @param bool $addEmptyValue Add also empty value if needed + * @param bool $reload Force reload options + * @return array + */ + public function getOptions($fieldInfos, $key, $addEmptyValue = false, $reload = false) + { + return parent::getOptions($fieldInfos, $key, $addEmptyValue, $reload); + } +} diff --git a/htdocs/core/class/fields/chkbxlstfield.class.php b/htdocs/core/class/fields/chkbxlstfield.class.php new file mode 100644 index 00000000000..a88a9badc9c --- /dev/null +++ b/htdocs/core/class/fields/chkbxlstfield.class.php @@ -0,0 +1,288 @@ + + * + * 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/fields/chkbxlstfield.class.php + * \ingroup core + * \brief File of class to chkbxlst field(multiselect) + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonsellistfield.class.php'; + + +/** + * Class to chkbxlst field (multiselect) + */ +class ChkbxlstField extends CommonSellistField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(array(), ''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + $values = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $optionsList = array(); + $options = $this->getOptions($fieldInfos, $key); + foreach ($options as $optionKey => $optionInfos) { + $options[$optionKey] = $optionInfos['label']; + } + + return self::$form->multiselectarray($htmlName, $optionsList, $values, 0, 0, $moreCss, 0, 0, $moreAttrib, '', '', (int) (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2'))); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + $values = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $options = $this->getOptions($fieldInfos, $key); + + return self::$form->multiselectarray($htmlName, $options, $values, 0, 0, $moreCss, 0, 0, $moreAttrib, '', '', (int) (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2'))); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + $values = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $out = ''; + if (!$this->isEmptyValue($fieldInfos, $values)) { + $options = $this->getOptions($fieldInfos, $key, false, false, $values); + $optionParams = $this->getOptionsParams($fieldInfos->options); + $isCategory = $optionParams['tableName'] == 'categorie' && !empty($optionParams['categoryType']); + + $toPrint = array(); + foreach ($values as $val) { + $valueToPrint = ''; + $colorToPrint = 'bbb'; + if (isset($options[$val])) { + $valueToPrint = $options[$val]['label']; + + if ($isCategory) { + require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php'; + $c = new Categorie($this->db); + $c->fetch($val); + $colorToPrint = $c->color ? $c->color : 'bbb'; + $valueToPrint = img_object('', 'category') . ' ' . $valueToPrint; + } + } else { + $valueToPrint = $val; + } + $toPrint[] = '
  • ' . $valueToPrint . '
  • '; + } + if (!empty($toPrint)) { + $out = '
      ' . implode('', $toPrint) . '
    '; + } + } + + return $out; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth400'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $values = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $result = parent::verifyFieldValue($fieldInfos, $key, $values); + if ($result && !$this->isEmptyValue($fieldInfos, $values)) { + $optionParams = $this->getOptionsParams($fieldInfos->options); + if (!self::$validator->isInDb($values, $optionParams['tableName'], $optionParams['keyField'])) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $values = GETPOST($htmlName, 'array'); + + return $this->verifyFieldValue($fieldInfos, $key, $values); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $values = GETPOST($htmlName, 'array'); + if (is_array($values)) $values = implode(',', $values); + } else { + $values = $defaultValue; + } + + return $values; + } + + /** + * 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'array'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!empty($value) && is_array($value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + + $tmp = "'" . implode("','", array_map(array($this->db, 'escape'), $value)) . "'"; + return " AND " . $field . " IN (" . $this->db->sanitize($tmp, 1) . ")"; + } + + return ''; + } + + /** + * Get list of options + * + * @param FieldInfos $fieldInfos Array of properties for field to show + * @param string $key Key of field + * @param bool $addEmptyValue Add also empty value if needed + * @param bool $reload Force reload options + * @param string|array $selectedValues Only selected values + * @return array + */ + public function getOptions($fieldInfos, $key, $addEmptyValue = false, $reload = false, $selectedValues = array()) + { + return parent::getOptions($fieldInfos, $key, $addEmptyValue, $reload, $selectedValues); + } +} diff --git a/htdocs/core/class/fields/commonfield.class.php b/htdocs/core/class/fields/commonfield.class.php new file mode 100644 index 00000000000..870c29d54e2 --- /dev/null +++ b/htdocs/core/class/fields/commonfield.class.php @@ -0,0 +1,371 @@ + + * + * 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/fields/commonfield.class.php + * \ingroup core + * \brief File of class to common field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/html.form.class.php'; +require_once DOL_DOCUMENT_ROOT . '/core/class/validate.class.php'; + + +/** + * Class to common field + */ +abstract class CommonField +{ + /** + * @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 string Type + */ + public $type; + + /** + * @var string Label + */ + public $label; + + /** + * @var Form|null Form handler. + */ + public static $form; + + /** + * @var Validate|null Validate handler. + */ + public static $validator; + + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array('', 0, array()); + + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + public function __construct($db) + { + global $form, $langs; + + $this->db = $db; + $this->error = ''; + $this->errors = array(); + + // Type and label + $this->type = strtolower(substr(get_class($this), 0, -5)); + $this->label = 'FieldLabel' . ucfirst($this->type); + + if (!isset(self::$form)) { + if (!is_object($form)) { + $form = new Form($this->db); + } + self::setForm($form); + } + + if (!isset(self::$validator)) { + // Use Validate class to allow external Modules to use data validation part instead of concentrate all test here (factoring) or just for reuse + $validator = new Validate($this->db, $langs); + self::setValidator($validator); + } + } + + /** + * Set form used for print the field + * + * @param Form $form Form handler + * @return void + */ + public static function setForm(&$form) + { + self::$form = &$form; + } + + /** + * Set validator used for check the field value + * + * @param Validate $validator Validate handler + * @return void + */ + public static function setValidator(&$validator) + { + self::$validator = &$validator; + } + + /** + * 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) : ''); + } + + /** + * Check if the value is deemed as empty + * + * @param FieldInfos $fieldInfos Properties of the 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) + * @param array $emptyValues List of value deemed as empty + * @return bool + */ + public function isEmptyValue($fieldInfos, $value, $emptyValues = null) + { + if (!isset($value)) { + return true; + } + + if (!is_array($emptyValues)) { + $emptyValues = !empty($fieldInfos->emptyValues) && is_array($fieldInfos->emptyValues) ? $fieldInfos->emptyValues : $this->emptyValues; + } + + foreach ($emptyValues as $val) { + if ($val === $value) { + return true; + } + } + + return false; + } + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return ''; + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return ''; + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + return ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = 'minwidth400') + { + if (empty($moreCss)) { + if (!empty($fieldInfos->css)) { + $moreCss = $fieldInfos->css; + } elseif (!empty($defaultCss)) { + $moreCss = $defaultCss; + } + } + $moreCss = trim((string) $moreCss); + + return empty($moreCss) ? '' : ' ' . $moreCss; + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + global $langs; + + $required = $fieldInfos->required; + $minLength = $fieldInfos->minLength ?? 0; + $maxLength = $fieldInfos->maxLength ?? 0; + //$emptyValues = !empty($fieldInfos->emptyValues) ? $fieldInfos->emptyValues : $this->emptyValues; + + // Clear error + self::$validator->error = ''; + + // Todo move this in validate class ? + // Required test and empty value + if ($this->isEmptyValue($fieldInfos, $value)) { + if ($required) { + self::$validator->error = $langs->trans('RequireANotEmptyValue'); + return false; + } else { + // if no value sent and the field is not mandatory, no need to perform tests + return true; + } + } + + // MIN Size test + if (!empty($minLength) && is_string($value) && !self::$validator->isMinLength($value, $minLength)) { + return false; + } + + // MAX Size test + if (!empty($maxLength) && is_string($value) && !self::$validator->isMaxLength($value, $maxLength)) { + return false; + } + + // Todo move this in validate class ? + // MIN Value test + if (isset($fieldInfos->minValue) && is_numeric($value) && ((double) $value) < $fieldInfos->minValue) { + self::$validator->error = $langs->trans('RequireMinValue', $fieldInfos->minValue); + return false; + } + + // MAX Value test + if (isset($fieldInfos->maxValue) && is_numeric($value) && ((double) $value) > $fieldInfos->maxValue) { + self::$validator->error = $langs->trans('RequireMaxValue', $fieldInfos->maxValue); + return false; + } + + return true; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + return $this->verifyFieldValue($fieldInfos, $key, GETPOST($htmlName, 'restricthtml')); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + return $defaultValue; + } + + /** + * 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + return $defaultValue; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + return ''; + } +} diff --git a/htdocs/core/class/fields/commongeofield.class.php b/htdocs/core/class/fields/commongeofield.class.php new file mode 100644 index 00000000000..0e739bd297e --- /dev/null +++ b/htdocs/core/class/fields/commongeofield.class.php @@ -0,0 +1,195 @@ + + * + * 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/fields/commongeofield.class.php + * \ingroup core + * \brief File of class to common geo field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to common geo field (for linestrg, multipts, point, polygon, ...) + */ +class CommonGeoField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + return ''; + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputGeoPoint($htmlName, (string) $value, $fieldInfos->type); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? self::$form->outputGeoPoint((string) $value, $fieldInfos->type) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + // Todo make the validator test for geo point + return parent::verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'restricthtml'); + if ($value != '{}') { + require_once DOL_DOCUMENT_ROOT . '/core/class/dolgeophp.class.php'; + $dolgeophp = new DolGeoPHP($this->db); + $value = $dolgeophp->getWkt($value); + } else { + $value = ''; + } + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + return ''; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + return ''; + } +} diff --git a/htdocs/core/class/fields/commonselectfield.class.php b/htdocs/core/class/fields/commonselectfield.class.php new file mode 100644 index 00000000000..e640d33008b --- /dev/null +++ b/htdocs/core/class/fields/commonselectfield.class.php @@ -0,0 +1,96 @@ + + * + * 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/fields/commonselectfield.class.php + * \ingroup core + * \brief File of class to common select field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to common select field + */ +class CommonSelectField extends CommonField +{ + /** + * @var array> Options cached + */ + public static $options = array(); + + /** + * Get list of options + * + * @param FieldInfos $fieldInfos Array of properties for field to show + * @param string $key Key of field + * @param bool $addEmptyValue Add also empty value if needed + * @param bool $reload Force reload options + * @return array + */ + public function getOptions($fieldInfos, $key, $addEmptyValue = false, $reload = false) + { + global $langs; + + if (!isset(self::$options[$key]) || $reload) { + $options = array(); + if (!empty($fieldInfos->options) && is_array($fieldInfos->options)) { + foreach ($fieldInfos->options as $optionKey => $optionLabel) { + $optionKey = (string) $optionKey; + $optionLabel = (string) $optionLabel; + if ($optionKey == '') { + continue; + } + + // Manage dependency list + $fieldValueParent = ''; + if (strpos($optionLabel, "|") !== false) { + list($optionLabel, $valueParent) = explode('|', $optionLabel); + $fieldValueParent = trim($fieldValueParent); + } + + if (empty($optionLabel)) { + $optionLabel = '(not defined)'; + } else { + $optionLabel = $langs->trans($optionLabel); + } + + $options[$optionKey] = array( + 'id' => $optionKey, + 'label' => $optionLabel, + 'parent' => $fieldValueParent, + ); + } + } + if ($addEmptyValue && (!$fieldInfos->required || count($options) > 1)) { + // For preserve the numeric key indexes + $options = array( + '' => array( + 'id' => '', + 'label' => ' ', + 'parent' => '', + ) + ) + $options; + } + + self::$options[$key] = $options; + } + + return self::$options[$key]; + } +} diff --git a/htdocs/core/class/fields/commonsellistfield.class.php b/htdocs/core/class/fields/commonsellistfield.class.php new file mode 100644 index 00000000000..b67805db073 --- /dev/null +++ b/htdocs/core/class/fields/commonsellistfield.class.php @@ -0,0 +1,373 @@ + + * + * 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/fields/commonsellistfield.class.php + * \ingroup core + * \brief File of class to common sellist field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to common sellist field + */ +class CommonSellistField extends CommonField +{ + /** + * @var string Url of the AJAX page for get options of the sellist + */ + public static $ajaxUrl = DOL_URL_ROOT . '/core/ajax/ajaxfield.php'; + + /** + * @var array> Options cached + */ + public static $options = array(); + + /** + * @var array Code mapping from ID. For backward compatibility + */ + const MAP_ID_TO_CODE = array( + 0 => 'product', + 1 => 'supplier', + 2 => 'customer', + 3 => 'member', + 4 => 'contact', + 5 => 'bank_account', + 6 => 'project', + 7 => 'user', + 8 => 'bank_line', + 9 => 'warehouse', + 10 => 'actioncomm', + 11 => 'website_page', + 12 => 'ticket', + 13 => 'knowledgemanagement', + 14 => 'fichinter', + 16 => 'order', + 17 => 'invoice', + 20 => 'supplier_order', + 21 => 'supplier_invoice' + ); + + /** + * Get all parameters in the options + * + * @param array $options Options of the field + * @return array{all:string,tableName:string,labelFullFields:string[],labelFields:string[],labelAlias:string[],keyField:string,parentName:string,parentFullField:string,parentField:string,parentAlias:string,filter:string,categoryType:string,categoryRoots:string,sortField:string} + */ + public function getOptionsParams($options) + { + $options = is_array($options) ? $options : array(); + $paramList = array_keys($options); + $paramList = preg_split('/[\r\n]+/', $paramList[0]); + // 0 : tableName + // 1 : label field name + // 2 : key fields name (if different of rowid) + // optional parameters... + // 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) + // 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 + // 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 (Don't manage ASC or DESC) + + $all = (string) $paramList[0]; + $InfoFieldList = explode(":", $all, 5); + + // 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); + $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] . ')'; + } + } + + $tableName = (string) ($InfoFieldList[0] ?? ''); + $labelFullFields = (string) ($InfoFieldList[1] ?? ''); + // @phpstan-ignore-next-line + $labelFullFields = array_filter(array_map('trim', explode('|', $labelFullFields)), 'strlen'); + $labelFields = array(); + $labelAlias = array(); + foreach ($labelFullFields as $labelFullField) { + $tmp = $this->getSqlFieldInfo($labelFullField); + $labelFields[] = $tmp['field']; + $labelAlias[] = $tmp['alias']; + } + $keyField = (string) ($InfoFieldList[2] ?? ''); + if (empty($keyField)) $keyField = 'rowid'; + $keyFieldParent = (string) ($InfoFieldList[3] ?? ''); + $tmp = array_map('trim', explode('|', $keyFieldParent)); + $parentName = (string) ($tmp[0] ?? ''); + $parentFullField = (string) ($tmp[1] ?? ''); + $tmp = $this->getSqlFieldInfo($parentFullField); + $parentField = $tmp['field']; + $parentAlias = $tmp['alias']; + $filter = (string) ($InfoFieldList[4] ?? ''); + $categoryType = (string) ($InfoFieldList[5] ?? ''); + if (is_numeric($categoryType)) { // deprecated: must use the category code instead of id. For backward compatibility. + require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php'; + $categoryType = self::MAP_ID_TO_CODE[(int) $categoryType] ?? ''; + } + $categoryRoots = (string) ($InfoFieldList[6] ?? ''); + $sortField = (string) ($InfoFieldList[7] ?? ''); + + return array( + 'all' => $all, + 'tableName' => $tableName, + 'labelFullFields' => $labelFullFields, + 'labelFields' => $labelFields, + 'labelAlias' => $labelAlias, + 'keyField' => $keyField, + 'parentName' => $parentName, + 'parentFullField' => $parentFullField, + 'parentField' => $parentField, + 'parentAlias' => $parentAlias, + 'filter' => $filter, + 'categoryType' => $categoryType, + 'categoryRoots' => $categoryRoots, + 'sortField' => $sortField, + ); + } + + /** + * Get sql info of the full field + * + * @param string $fullField Full field (ex: p.test AS label or f(a,b,c) AS label) + * @return array{field:string,alias:string} + */ + public function getSqlFieldInfo($fullField) + { + if (preg_match('/(.*)\s+AS\s+(\w+)$/i', $fullField, $matches)) { + $field = $matches[1]; + $alias = $matches[2]; + } else { + $field = $fullField; + $alias = $fullField; + } + + if (preg_match('/^\w+\.(.*)/i', $field, $matches)) { + $alias = $matches[1]; + } + + return array( + 'field' => $field, + 'alias' => $alias, + ); + } + + /** + * Get list of options + * + * @param FieldInfos $fieldInfos Array of properties for field to show + * @param string $key Key of field + * @param bool $addEmptyValue Add also empty value if needed + * @param bool $reload Force reload options + * @param string|array $selectedValues Only selected values + * @return array|null Return null if error + */ + public function getOptions($fieldInfos, $key, $addEmptyValue = false, $reload = false, $selectedValues = array()) + { + global $conf, $langs; + + $selectedValues = array_map('trim', is_array($selectedValues) ? $selectedValues : array($selectedValues)); + + if (!isset(self::$options[$key]) || $reload) { + $options = array(); + if (!empty($fieldInfos->options) && is_array($fieldInfos->options)) { + $optionsParams = $this->getOptionsParams($fieldInfos->options); + + if ($optionsParams['tableName'] == 'categorie' && !empty($optionsParams['categoryType'])) { + $data = self::$form->select_all_categories($optionsParams['categoryType'], '', 'parent', 64, $optionsParams['categoryRoots'], 1, 1); + if (is_array($data)) { + foreach ($data as $data_key => $data_value) { + $options[$data_key] = array( + 'label' => $data_value, + 'parent' => '', + ); + } + } + } else { + $filter = $optionsParams['filter']; + $hasExtra = !empty($filter) && strpos($filter, 'extra.') !== false; + $keyField = ($hasExtra ? 'main.' : '') . $optionsParams['keyField']; + + $keyList = $keyField . ' AS rowid'; + if (!empty($optionsParams['parentFullField'])) { + $keyList .= ', ' . $optionsParams['parentFullField']; + } + if (!empty($optionsParams['labelFullFields'])) { + $keyList .= ', ' . implode(', ', $optionsParams['labelFullFields']); + } + + $sql = "SELECT " . $keyList; + $sql .= " FROM " . $this->db->sanitize($this->db->prefix() . $optionsParams['tableName']); + if ($hasExtra) { + $sql .= " AS main"; + $sql .= " LEFT JOIN " . $this->db->sanitize($this->db->prefix() . $optionsParams['tableName']) . "_extrafields AS extra ON extra.fk_object = " . $keyField; + } + + // Add filter from 4th field + if (!empty($filter)) { + // can use current entity filter + if (strpos($filter, '$ENTITY$') !== false) { + $filter = str_replace('$ENTITY$', (string) $conf->entity, $filter); + } + // can use SELECT request + if (strpos($filter, '$SEL$') !== false && !getDolGlobalString("MAIN_DISALLOW_UNSECURED_SELECT_INTO_EXTRAFIELDS_FILTER")) { + $filter = str_replace('$SEL$', 'SELECT', $filter); + } + // can use MODE parameter (list or view) + if (strpos($filter, '$MODE$') !== false) { + $filter = str_replace('$MODE$', empty($fieldInfos->mode) ? 'view' : $fieldInfos->mode, $filter); + } + + // Current object id can be used into filter + $objectid = isset($fieldInfos->otherParams['objectId']) ? (int) $fieldInfos->otherParams['objectId'] : (isset($fieldInfos->object) && is_object($fieldInfos->object) ? (int) $fieldInfos->object->id : 0); + if (strpos($filter, '$ID$') !== false && !empty($objectid)) { + $filter = str_replace('$ID$', (string) $objectid, $filter); + } elseif (substr($_SERVER["PHP_SELF"], -8) == 'list.php') { + // In filters of list views, we do not want $ID$ replaced by 0. So we remove the '=' condition. + // Do nothing if condition is using 'IN' keyword + // Replace 'column = $ID$' by "word" + $filter = preg_replace('#\b([a-zA-Z0-9-\.-_]+)\b *= *\$ID\$#', '$1', $filter); + // Replace '$ID$ = column' by "word" + $filter = preg_replace('#\$ID\$ *= *\b([a-zA-Z0-9-\.-_]+)\b#', '$1', $filter); + } else { + $filter = str_replace('$ID$', '0', $filter); + } + + // can use filter on any field of object + if (isset($fieldInfos->object) && is_object($fieldInfos->object)) { + $object = $fieldInfos->object; + $tags = []; + preg_match_all('/\$(.*?)\$/', $filter, $tags); // Example: $filter is ($dateadh$:<=:CURRENT_DATE) + foreach ($tags[0] as $keytag => $valuetag) { + $property = preg_replace('/[^a-z0-9_]/', '', strtolower($tags[1][$keytag])); + if (strpos($filter, $valuetag) !== false && property_exists($object, $property) && !empty($object->$property)) { + $filter = str_replace($valuetag, (string) $object->$property, $filter); + } else { + $filter = str_replace($valuetag, '0', $filter); + } + } + } + + $errstr = ''; + $sql .= " WHERE " . forgeSQLFromUniversalSearchCriteria($filter, $errstr, 1); + } else { + $sql .= ' WHERE 1=1'; + } + // Some tables may have field, some other not. For the moment we disable it. + if (in_array($optionsParams['tableName'], array('tablewithentity'))) { + $sql .= " AND entity = " . ((int) $conf->entity); + } + // Manage dependency list (from AJAX) + if (isset($fieldInfos->optionsSqlDependencyValue)) { + // TODO rework for dependency with a date or a multiselect + $sql .= " AND " . $optionsParams['parentField'] . " = '" . $this->db->escape($fieldInfos->optionsSqlDependencyValue) . "'"; + } + // Only selected values + if (!empty($selectedValues)) { + $tmp = "'" . implode("','", array_map(array($this->db, 'escape'), $selectedValues)) . "'"; + $sql .= " AND " . $keyField . " IN (" . $this->db->sanitize($tmp, 1) . ")"; + } + + // Note: $InfoFieldList can be 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:Sortfield]]]]]]' + if (preg_match('/^[a-z0-9_\-,]+$/i', $optionsParams['sortField'])) { + $sql .= $this->db->order($optionsParams['sortField']); + } else { + $sql .= $this->db->order(implode(', ', $optionsParams['labelFields'])); + } + + $limit = getDolGlobalInt('MAIN_EXTRAFIELDS_LIMIT_SELLIST_SQL', $fieldInfos->optionsSqlLimit ?? 1000); + $offset = $fieldInfos->optionsSqlOffset ?? (isset($fieldInfos->optionsSqlPage) ? (((int) $fieldInfos->optionsSqlPage) - 1) * $limit : 0); + $sql .= $this->db->plimit($limit, $offset); + + dol_syslog(get_class($this) . '::getOptions', LOG_DEBUG); + $resql = $this->db->query($sql); + if ($resql) { + while ($obj = $this->db->fetch_object($resql)) { + $optionKey = (string) $obj->rowid; + + $toPrint = array(); + foreach ($optionsParams['labelAlias'] as $fieldToShow) { + $toPrint[] = is_string($obj->$fieldToShow) ? $langs->trans($obj->$fieldToShow) : $obj->$fieldToShow; + } + $optionLabel = implode(' ', $toPrint); + + if (empty($optionLabel)) { + $optionLabel = '(not defined)'; + } + + // Manage dependency list + $fieldValueParent = !empty($optionsParams['parentName']) && !empty($optionsParams['parentAlias']) ? $optionsParams['parentName'] . ':' . ((string) $obj->{$optionsParams['parentAlias']}) : ''; + + $options[$optionKey] = array( + 'id' => $optionKey, + 'label' => $optionLabel, + 'parent' => $fieldValueParent, + ); + } + $this->db->free($resql); + } else { + $this->error = 'Error in request ' . $sql . ' ' . $this->db->lasterror() . '. Check setup of extra parameters.
    '; + return null; + } + } + } + if ($addEmptyValue && (!$fieldInfos->required || count($options) > 1)) { + // For preserve the numeric key indexes + $options = array( + '' => array( + 'id' => '', + 'label' => ' ', + 'parent' => '', + ) + ) + $options; + } + + self::$options[$key] = $options; + } + + $options = self::$options[$key]; + // Only selected values + if (!empty($selectedValues)) { + $options = array_intersect_key($options, array_flip($selectedValues)); + } + + return $options; + } +} diff --git a/htdocs/core/class/fields/datefield.class.php b/htdocs/core/class/fields/datefield.class.php new file mode 100644 index 00000000000..191f923c30e --- /dev/null +++ b/htdocs/core/class/fields/datefield.class.php @@ -0,0 +1,261 @@ + + * + * 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/fields/datefield.class.php + * \ingroup core + * \brief File of class to date field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to date field + */ +class DateField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + + // TODO Must also support $moreCss ? + + $htmlName = $keyPrefix . $key . $keySuffix; + $prefill = array( + 'start' => $value['start'] ?? '', + 'end' => $value['end'] ?? '', + ); + + // Search filter on a date field shows two inputs to select a date range + $out = '
    '; + $out .= self::$form->selectDate($prefill['start'], $htmlName . '_start', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From")); + $out .= '
    '; + $out .= self::$form->selectDate($prefill['end'], $htmlName . '_end', 0, 0, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to")); + $out .= '
    '; + + 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 field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $required = $fieldInfos->required ? 1 : 0; + $htmlName = $keyPrefix . $key . $keySuffix; + + // Do not show current date when field not required (see selectDate() method) + if (!$required && $this->isEmptyValue($fieldInfos, $value)) { + $value = '-1'; + } + + // TODO Must also support $moreAttrib and $moreCss ? + return self::$form->selectDate($value, $htmlName, 0, 0, $required, '', 1, 1, 0, 1); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + // We suppose dates without time are always gmt (storage of course + output) + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_date($value, 'day') : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth100imp'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isTimestamp($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . 'month') || GETPOSTISSET($htmlName . 'day') || GETPOSTISSET($htmlName . 'year')) { + $value = dol_mktime(12, 0, 0, GETPOSTINT($htmlName . 'month'), GETPOSTINT($htmlName . 'day'), GETPOSTINT($htmlName . 'year')); // for date without hour, we use gmt + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . '_startmonth') || GETPOSTISSET($htmlName . '_startday') || GETPOSTISSET($htmlName . '_startyear')) { + $start = dol_mktime(0, 0, 0, GETPOSTINT($htmlName . '_startmonth'), GETPOSTINT($htmlName . '_startday'), GETPOSTINT($htmlName . '_startyear')); + } else { + $start = is_array($defaultValue) && isset($defaultValue['start']) ? $defaultValue['start'] : ''; + } + + if (GETPOSTISSET($htmlName . '_endmonth') || GETPOSTISSET($htmlName . '_endday') || GETPOSTISSET($htmlName . '_endyear')) { + $end = dol_mktime(23, 59, 59, GETPOSTINT($htmlName . '_endmonth'), GETPOSTINT($htmlName . '_endday'), GETPOSTINT($htmlName . '_endyear')); + } else { + $end = is_array($defaultValue) && isset($defaultValue['end']) ? $defaultValue['end'] : ''; + } + + return array( + 'start' => $start, + 'end' => $end, + ); + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + $sql = ''; + + if (is_array($value)) { + $hasStart = !is_null($value['start']) && $value['start'] !== ''; + $hasEnd = !is_null($value['start']) && $value['start'] !== ''; + if ($hasStart && $hasEnd) { + $sql = " AND (" . $field . " BETWEEN '" . $this->db->idate($value['start']) . "' AND '" . $this->db->idate($value['end']) . "')"; + } elseif ($hasStart) { + $sql = " AND " . $field . " >= '" . $this->db->idate($value['start']) . "'"; + } elseif ($hasEnd) { + $sql = " AND " . $field . " <= '" . $this->db->idate($value['end']) . "'"; + } + } elseif (is_numeric($value)) { + include_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php'; + $value = dol_get_first_hour($value); + $sql = " AND " . $field . " = '" . $this->db->idate($value) . "'"; + } + + return $sql; + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/datetimefield.class.php b/htdocs/core/class/fields/datetimefield.class.php new file mode 100644 index 00000000000..6e29a76fb7a --- /dev/null +++ b/htdocs/core/class/fields/datetimefield.class.php @@ -0,0 +1,258 @@ + + * + * 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/fields/datetimefield.class.php + * \ingroup core + * \brief File of class to datetime field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to datetime field + */ +class DatetimeField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + + // TODO Must also support $moreCss ? + + $htmlName = $keyPrefix . $key . $keySuffix; + $prefill = array( + 'start' => $value['start'] ?? '', + 'end' => $value['end'] ?? '', + ); + + // Search filter on a date field shows two inputs to select a date range + $out = '
    '; + $out .= self::$form->selectDate($prefill['start'], $htmlName . '_start', 1, 1, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From"), 'tzuserrel'); + $out .= '
    '; + $out .= self::$form->selectDate($prefill['end'], $htmlName . '_end', 1, 1, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"), 'tzuserrel'); + $out .= '
    '; + + 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 field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $required = $fieldInfos->required ? 1 : 0; + $htmlName = $keyPrefix . $key . $keySuffix; + + // Do not show current date when field not required (see selectDate() method) + if (!$required && $this->isEmptyValue($fieldInfos, $value)) { + $value = '-1'; + } + + // TODO Must also support $moreAttrib and $moreCss ? + return self::$form->selectDate($value, $htmlName, 1, 1, $required, '', 1, 1, 0, 1, '', '', '', 1, '', '', 'tzuserrel'); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_date($value, 'dayhour', 'tzuserrel') : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth200imp'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isTimestamp($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . 'hour') || GETPOSTISSET($htmlName . 'min') || GETPOSTISSET($htmlName . 'sec') || GETPOSTISSET($htmlName . 'month') || GETPOSTISSET($htmlName . 'day') || GETPOSTISSET($htmlName . 'year')) { + $value = dol_mktime(GETPOSTINT($htmlName . 'hour'), GETPOSTINT($htmlName . 'min'), GETPOSTINT($htmlName . 'sec'), GETPOSTINT($htmlName . 'month'), GETPOSTINT($htmlName . 'day'), GETPOSTINT($htmlName . 'year'), 'tzuserrel'); // for date without hour, we use gmt + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . '_startmonth') || GETPOSTISSET($htmlName . '_startday') || GETPOSTISSET($htmlName . '_startyear')) { + $start = dol_mktime(0, 0, 0, GETPOSTINT($htmlName . '_startmonth'), GETPOSTINT($htmlName . '_startday'), GETPOSTINT($htmlName . '_startyear'), 'tzuserrel'); + } else { + $start = is_array($defaultValue) && isset($defaultValue['start']) ? $defaultValue['start'] : ''; + } + + if (GETPOSTISSET($htmlName . '_endmonth') || GETPOSTISSET($htmlName . '_endday') || GETPOSTISSET($htmlName . '_endyear')) { + $end = dol_mktime(23, 59, 59, GETPOSTINT($htmlName . '_endmonth'), GETPOSTINT($htmlName . '_endday'), GETPOSTINT($htmlName . '_endyear'), 'tzuserrel'); + } else { + $end = is_array($defaultValue) && isset($defaultValue['end']) ? $defaultValue['end'] : ''; + } + + return array( + 'start' => $start, + 'end' => $end, + ); + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + $sql = ''; + + if (is_array($value)) { + $hasStart = !is_null($value['start']) && $value['start'] !== ''; + $hasEnd = !is_null($value['start']) && $value['start'] !== ''; + if ($hasStart && $hasEnd) { + $sql = " AND (" . $field . " BETWEEN '" . $this->db->idate($value['start']) . "' AND '" . $this->db->idate($value['end']) . "')"; + } elseif ($hasStart) { + $sql = " AND " . $field . " >= '" . $this->db->idate($value['start']) . "'"; + } elseif ($hasEnd) { + $sql = " AND " . $field . " <= '" . $this->db->idate($value['end']) . "'"; + } + } elseif (is_numeric($value)) { + $sql = " AND " . $field . " = '" . $this->db->idate($value) . "'"; + } + + return $sql; + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/datetimegmtfield.class.php b/htdocs/core/class/fields/datetimegmtfield.class.php new file mode 100644 index 00000000000..445b92d4349 --- /dev/null +++ b/htdocs/core/class/fields/datetimegmtfield.class.php @@ -0,0 +1,258 @@ + + * + * 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/fields/datetimegmtfield.class.php + * \ingroup core + * \brief File of class to datetimegmt field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to datetimegmt field + */ +class DatetimegmtField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + + // TODO Must also support $moreCss ? + + $htmlName = $keyPrefix . $key . $keySuffix; + $prefill = array( + 'start' => $value['start'] ?? '', + 'end' => $value['end'] ?? '', + ); + + // Search filter on a date field shows two inputs to select a date range + $out = '
    '; + $out .= self::$form->selectDate($prefill['start'], $htmlName . '_start', 1, 1, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From"), 'gmt'); + $out .= '
    '; + $out .= self::$form->selectDate($prefill['end'], $htmlName . '_end', 1, 1, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"), 'gmt'); + $out .= '
    '; + + 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 field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $required = $fieldInfos->required ? 1 : 0; + $htmlName = $keyPrefix . $key . $keySuffix; + + // Do not show current date when field not required (see selectDate() method) + if (!$required && $this->isEmptyValue($fieldInfos, $value)) { + $value = '-1'; + } + + // TODO Must also support $moreAttrib and $moreCss ? + return self::$form->selectDate($value, $htmlName, 1, 1, $required, '', 1, 1, 0, 1, '', '', '', 1, '', '', 'gmt'); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_date($value, 'dayhour', 'gmt') : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth200imp'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isTimestamp($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . 'hour') || GETPOSTISSET($htmlName . 'min') || GETPOSTISSET($htmlName . 'sec') || GETPOSTISSET($htmlName . 'month') || GETPOSTISSET($htmlName . 'day') || GETPOSTISSET($htmlName . 'year')) { + $value = dol_mktime(GETPOSTINT($htmlName . 'hour'), GETPOSTINT($htmlName . 'min'), GETPOSTINT($htmlName . 'sec'), GETPOSTINT($htmlName . 'month'), GETPOSTINT($htmlName . 'day'), GETPOSTINT($htmlName . 'year'), 'gmt'); // for date without hour, we use gmt + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . '_startmonth') || GETPOSTISSET($htmlName . '_startday') || GETPOSTISSET($htmlName . '_startyear')) { + $start = dol_mktime(0, 0, 0, GETPOSTINT($htmlName . '_startmonth'), GETPOSTINT($htmlName . '_startday'), GETPOSTINT($htmlName . '_startyear'), 'gmt'); + } else { + $start = is_array($defaultValue) && isset($defaultValue['start']) ? $defaultValue['start'] : ''; + } + + if (GETPOSTISSET($htmlName . '_endmonth') || GETPOSTISSET($htmlName . '_endday') || GETPOSTISSET($htmlName . '_endyear')) { + $end = dol_mktime(23, 59, 59, GETPOSTINT($htmlName . '_endmonth'), GETPOSTINT($htmlName . '_endday'), GETPOSTINT($htmlName . '_endyear'), 'gmt'); + } else { + $end = is_array($defaultValue) && isset($defaultValue['end']) ? $defaultValue['end'] : ''; + } + + return array( + 'start' => $start, + 'end' => $end, + ); + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + $sql = ''; + + if (is_array($value)) { + $hasStart = !is_null($value['start']) && $value['start'] !== ''; + $hasEnd = !is_null($value['start']) && $value['start'] !== ''; + if ($hasStart && $hasEnd) { + $sql = " AND (" . $field . " BETWEEN '" . $this->db->idate($value['start']) . "' AND '" . $this->db->idate($value['end']) . "')"; + } elseif ($hasStart) { + $sql = " AND " . $field . " >= '" . $this->db->idate($value['start']) . "'"; + } elseif ($hasEnd) { + $sql = " AND " . $field . " <= '" . $this->db->idate($value['end']) . "'"; + } + } elseif (is_numeric($value)) { + $sql = " AND " . $field . " = '" . $this->db->idate($value) . "'"; + } + + return $sql; + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/doublefield.class.php b/htdocs/core/class/fields/doublefield.class.php new file mode 100644 index 00000000000..c1a80ee2414 --- /dev/null +++ b/htdocs/core/class/fields/doublefield.class.php @@ -0,0 +1,220 @@ + + * + * 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/fields/doublefield.class.php + * \ingroup core + * \brief File of class to double field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to double field + */ +class DoubleField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $value = !$this->isEmptyValue($fieldInfos, $value) ? price((double) $value) : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, $value, $htmlName, $moreCss, $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? price((double) $value) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'maxwidth75'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isNumeric($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml'); + $value = str_replace(',', '.', $value); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = (double) price2num(GETPOST($htmlName, 'alphanohtml')); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 1); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/durationfield.class.php b/htdocs/core/class/fields/durationfield.class.php new file mode 100644 index 00000000000..3ccc6235ff5 --- /dev/null +++ b/htdocs/core/class/fields/durationfield.class.php @@ -0,0 +1,207 @@ + + * + * 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/fields/durationfield.class.php + * \ingroup core + * \brief File of class to duration field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; +require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php'; + + +/** + * Class to duration field + */ +class DurationField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + // Todo make filter with min / max ? or same as a number ? + return $this->printInputField($fieldInfos, $key, $value, $keyPrefix, $keySuffix, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + // TODO Must also support $moreAttrib and $moreCss ? + return self::$form->select_duration($htmlName, (int) $value, 0, 'text', 0, 1); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? convertSecondToTime((int) $value, 'allhourmin') : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isDuration($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . 'hour') || GETPOSTISSET($htmlName . 'min')) { + $value_hours = GETPOSTINT($htmlName . "hour"); + $value_minutes = GETPOSTINT($htmlName . "min"); + $value = $value_hours * 3600 + $value_minutes * 60; + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + return $this->getPostFieldValue($fieldInfos, $key, $defaultValue, $keyPrefix, $keySuffix); + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/emailfield.class.php b/htdocs/core/class/fields/emailfield.class.php new file mode 100644 index 00000000000..f2b73df8461 --- /dev/null +++ b/htdocs/core/class/fields/emailfield.class.php @@ -0,0 +1,217 @@ + + * + * 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/fields/emailfield.class.php + * \ingroup core + * \brief File of class to email field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to email field + */ +class EmailField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_email((string) $value, 0, 0, 0, 64, 1, 1) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isEmail($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/htmlfield.class.php b/htdocs/core/class/fields/htmlfield.class.php new file mode 100644 index 00000000000..58095d9f529 --- /dev/null +++ b/htdocs/core/class/fields/htmlfield.class.php @@ -0,0 +1,217 @@ + + * + * 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/fields/htmlfield.class.php + * \ingroup core + * \brief File of class to html field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +// TODO same as text field ? + +/** + * Class to html field + */ +class HtmlField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputHtml($htmlName, $value, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + if (!empty($fieldInfos->options)) { + $value = str_replace(',', "\n", (string) $value); + } + + return dol_htmlentitiesbr((string) $value); + } + + return ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + return parent::verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, empty($fieldInfos->getPostCheck) ? 'restricthtml' : $fieldInfos->getPostCheck); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/iconfield.class.php b/htdocs/core/class/fields/iconfield.class.php new file mode 100644 index 00000000000..8d1c9b9d8ed --- /dev/null +++ b/htdocs/core/class/fields/iconfield.class.php @@ -0,0 +1,211 @@ + + * + * 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/fields/iconfield.class.php + * \ingroup core + * \brief File of class to icon field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to icon field + */ +class IconField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $placeHolder = $fieldInfos->inputPlaceholder; + if (!empty($placeHolder)) $placeHolder = ' placeholder="' . dolPrintHTMLForAttribute($placeHolder) . '"'; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputIcon($htmlName, (string) $value, $moreCss, $moreAttrib . $placeHolder . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? self::$form->outputIcon((string) $value) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + return parent::verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $check = $key == 'lang' ? 'aZ09' : 'alphanohtml'; + $value = GETPOST($htmlName, $check); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/intfield.class.php b/htdocs/core/class/fields/intfield.class.php new file mode 100644 index 00000000000..605edd47ce9 --- /dev/null +++ b/htdocs/core/class/fields/intfield.class.php @@ -0,0 +1,218 @@ + + * + * 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/fields/intfield.class.php + * \ingroup core + * \brief File of class to int field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to int field + */ +class IntField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $size = (int) $fieldInfos->size; + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $maxLength = ''; // $size > 0 ? ' maxlength="' . $size . '"' : ''; // TODO rework, wrong method + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + $value = !is_null($value) && $value !== '' ? (int) $value : ''; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $maxLength . $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? (string) $value : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'maxwidth75'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isNumeric($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = (int) price2num(GETPOSTINT($htmlName)); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 1); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/ipfield.class.php b/htdocs/core/class/fields/ipfield.class.php new file mode 100644 index 00000000000..9484705a0c4 --- /dev/null +++ b/htdocs/core/class/fields/ipfield.class.php @@ -0,0 +1,222 @@ + + * + * 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/fields/ipfield.class.php + * \ingroup core + * \brief File of class to ip field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to ip field + */ +class IpField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_ip((string) $value, 0) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + global $langs; + + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + // TODO move to class Validate + if (!filter_var($value, FILTER_VALIDATE_IP)) { + self::$validator->error = $langs->trans('RequireValidValue'); + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/linestrgfield.class.php b/htdocs/core/class/fields/linestrgfield.class.php new file mode 100644 index 00000000000..8bab9b03677 --- /dev/null +++ b/htdocs/core/class/fields/linestrgfield.class.php @@ -0,0 +1,32 @@ + + * + * 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/fields/linestrgfield.class.php + * \ingroup core + * \brief File of class to linestrg field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commongeofield.class.php'; + + +/** + * Class to linestrg field + */ +class LinestrgField extends CommonGeoField +{ +} diff --git a/htdocs/core/class/fields/linkfield.class.php b/htdocs/core/class/fields/linkfield.class.php new file mode 100644 index 00000000000..caaa372c172 --- /dev/null +++ b/htdocs/core/class/fields/linkfield.class.php @@ -0,0 +1,437 @@ + + * + * 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/fields/linkfield.class.php + * \ingroup core + * \brief File of class to link field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to link field + */ +class LinkField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array('', '-1', '0', 0); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + $optionParams = $this->getOptionsParams($fieldInfos->options); + + if (version_compare(DOL_VERSION, '19.0.0') < 0) { + // Example: 'ObjectName:classPath:1:(status:=:1)' + $objectDesc = $optionParams['all']; + if (strpos($objectDesc, '$ID$') !== false && !empty($fieldInfos->object->id)) { + $objectDesc = str_replace('$ID$', (string) $fieldInfos->object->id, $objectDesc); + } + + $out = self::$form->selectForForms($objectDesc, $htmlName, (int) $value, 0, '', '', $moreCss, $moreAttrib); + } else { + // Example: 'ObjectName:classPath' To not propagate any filter (selectForForms do ajax call and propagating SQL filter is blocked by some WAF). + // Also we should use the one into the definition in the ->fields of $elem if found. + $objectDesc = $optionParams['objectClass'] . ':' . $optionParams['pathToClass']; + + // Example: 'actioncomm:options_fff' To be used in priority to know object linked with all its definition (including filters) + // The selectForForms is called with parameter $objectfield defined, so the app can retrieve the filter inside the ajax component instead of being provided as parameters. The + // filter was used 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. + $objectField = isset($fieldInfos->object) ? $fieldInfos->object->element . (!empty($fieldInfos->object->module) ? '@' . $fieldInfos->object->module : '') . ':' . ($fieldInfos->fieldType == FieldInfos::FIELD_TYPE_EXTRA_FIELD ? 'options_' : '') . $fieldInfos->nameInClass : ''; + + $out = self::$form->selectForForms($objectDesc, $htmlName, (int) $value, 0, '', '', $moreCss, $moreAttrib, 0, 0, '', $objectField); + } + + 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 field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $placeHolder = $fieldInfos->inputPlaceholder; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + $showEmpty = $fieldInfos->required && !$this->isEmptyValue($fieldInfos, $fieldInfos->defaultValue) ? 0 : 1; + + $optionParams = $this->getOptionsParams($fieldInfos->options); + + // If we have to add a create button + if ($optionParams['addCreateButton']) { + if (!empty($fieldInfos->picto)) { + $moreCss .= ' widthcentpercentminusxx'; + } else { + $moreCss .= ' widthcentpercentminusx'; + } + } elseif (!empty($fieldInfos->picto)) { + $moreCss .= ' widthcentpercentminusx'; + } + + if (version_compare(DOL_VERSION, '19.0.0') < 0) { + // Example: 'ObjectName:classPath:1:(status:=:1)' + $objectDesc = $optionParams['all']; + if (strpos($objectDesc, '$ID$') !== false && !empty($fieldInfos->object->id)) { + $objectDesc = str_replace('$ID$', (string) $fieldInfos->object->id, $objectDesc); + } + + $out = self::$form->selectForForms($objectDesc, $htmlName, (int) $value, $showEmpty, '', $placeHolder, $moreCss, $moreAttrib . $autoFocus, 0, $fieldInfos->inputDisabled ? 1 : 0); + } else { + // Example: 'ObjectName:classPath' To not propagate any filter (selectForForms do ajax call and propagating SQL filter is blocked by some WAF). + // Also we should use the one into the definition in the ->fields of $elem if found. + $objectDesc = $optionParams['objectClass'] . ':' . $optionParams['pathToClass']; + + // Example: 'actioncomm:options_fff' To be used in priority to know object linked with all its definition (including filters) + // The selectForForms is called with parameter $objectfield defined, so the app can retrieve the filter inside the ajax component instead of being provided as parameters. The + // filter was used 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. + $objectField = isset($fieldInfos->object) ? $fieldInfos->object->element . (!empty($fieldInfos->object->module) ? '@' . $fieldInfos->object->module : '') . ':' . ($fieldInfos->fieldType == FieldInfos::FIELD_TYPE_EXTRA_FIELD ? 'options_' : '') . $fieldInfos->nameInClass : ''; + + $out = self::$form->selectForForms($objectDesc, $htmlName, (int) $value, $showEmpty, '', $placeHolder, $moreCss, $moreAttrib . $autoFocus, 0, $fieldInfos->inputDisabled ? 1 : 0, '', $objectField); + } + + if ($optionParams['addCreateButton'] && // If we have to add a create button + (!GETPOSTISSET('backtopage') || strpos(GETPOST('backtopage'), $_SERVER['PHP_SELF']) === 0) && // To avoid to open several times the 'Plus' button (we accept only one level) + !$fieldInfos->inputDisabled && // To avoid to show the button if the field is protected by a "disabled". + empty($fieldInfos->otherParams['nonewbutton']) // manually disable new button + ) { + $class = $optionParams['objectClass']; + $classfile = $optionParams['pathToClass']; + $classpath = dirname(dirname($classfile)); + if (file_exists(dol_buildpath($classpath . '/card.php'))) { + $url_path = dol_buildpath($classpath . '/card.php', 1); + } else { + $url_path = dol_buildpath($classpath . '/' . strtolower($class) . '_card.php', 1); + } + $paramforthenewlink = ''; + $paramforthenewlink .= (GETPOSTISSET('action') ? '&action=' . GETPOST('action', 'aZ09') : ''); + $paramforthenewlink .= (GETPOSTISSET('id') ? '&id=' . GETPOSTINT('id') : ''); + $paramforthenewlink .= (GETPOSTISSET('origin') ? '&origin=' . GETPOST('origin', 'aZ09') : ''); + $paramforthenewlink .= (GETPOSTISSET('originid') ? '&originid=' . GETPOSTINT('originid') : ''); + $paramforthenewlink .= '&fk_' . strtolower($class) . '=--IDFORBACKTOPAGE--'; + // TODO Add JavaScript code to add input fields already filled into $paramforthenewlink so we won't loose them when going back to main page + $out .= ''; + } + + return $out; + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $optionParams = $this->getOptionsParams($fieldInfos->options); + + $classpath = $optionParams['pathToClass']; + if (!empty($classpath)) { + $classname = $optionParams['objectClass']; + + $object = $this->getObject($classname, $classpath); + if (isset($object)) { + '@phan-var-force CommonObject $object'; + if ($object->element === 'product') { // Special case for product because default valut of fetch are wrong + '@phan-var-force Product $object'; + $result = $object->fetch((int) $value, '', '', '', 0, 1, 1); + } else { + $result = $object->fetch($value); + } + if ($result > 0) { + $getNomUrlParam1 = $optionParams['getNomUrlParam1']; + $getNomUrlParam2 = $optionParams['getNomUrlParam2']; + + if ($object->element === 'product') { + '@phan-var-force Product $object'; + $get_name_url_param_arr = array($getNomUrlParam1, $getNomUrlParam2, 0, -1, 0, '', 0, ' - '); + if (isset($fieldInfos->getNameUrlParams)) { + $get_name_url_params = explode(':', $fieldInfos->getNameUrlParams); + if (!empty($get_name_url_params)) { + $param_num_max = count($get_name_url_param_arr) - 1; + foreach ($get_name_url_params as $param_num => $param_value) { + if ($param_num > $param_num_max) { + break; + } + $get_name_url_param_arr[$param_num] = $param_value; + } + } + } + + /** + * @var Product $object + */ + return self::$form->getNomUrl($object, (int) $get_name_url_param_arr[0], $get_name_url_param_arr[1], (int) $get_name_url_param_arr[2], (int) $get_name_url_param_arr[3], (int) $get_name_url_param_arr[4], $get_name_url_param_arr[5], (int) $get_name_url_param_arr[6], $get_name_url_param_arr[7]); + } elseif (get_class($object) == 'Categorie') { + // For category object, rendering must use the same method than the one deinfed into showCategories() + $color = $object->color; + $sfortag = ''; + $sfortag .= self::$form->getNomUrl($object, (int) $getNomUrlParam1, $getNomUrlParam2); + $sfortag .= ''; + return $sfortag; + } else { + return self::$form->getNomUrl($object, (int) $getNomUrlParam1, $getNomUrlParam2); + } + } + } else { + dol_syslog('Error bad setup of field : ' . $key, LOG_WARNING); + return 'Error bad setup of field'; + } + } else { + dol_syslog('Error bad setup of field : ' . $key, LOG_WARNING); + return 'Error bad setup of field'; + } + } + + return ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth200imp'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + $optionParams = $this->getOptionsParams($fieldInfos->options); + $classname = $optionParams['objectClass']; + $classpath = $optionParams['pathToClass']; + $object = $this->getObject($classname, $classpath); + if (isset($object) && method_exists($object, 'isExistingObject') && !self::$validator->isFetchable((int) $value, $classname, $classpath) // All class don't have isExistingObject function ... + && (version_compare(DOL_VERSION, '19.0.0') < 0 || !self::$validator->isFetchableElement((int) $value, $classname)) // from V19 of Dolibarr, In some cases link use element instead of class, example project_task + ) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOSTINT($htmlName); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 2); + } + + return ''; + } + + /** + * Get all parameters in the options + * + * @param array $options Options of the field + * @return array{all:string,objectClass:string,pathToClass:string,addCreateButton:bool,getNomUrlParam1:string,getNomUrlParam2:string,filter:string,sortField:string} + */ + public function getOptionsParams($options) + { + $options = is_array($options) ? $options : array(); + $paramList = array_keys($options); + // Example: $paramList[0] = 'ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]' + // Example: $paramList[0] = 'ObjectClass:PathToClass:#getnomurlparam1=-1#getnomurlparam2=customer' + // Example: $paramList[0] = 'ObjectName:classPath' but can also be 'ObjectName:classPath:1:(status:=:1)' + + $all = (string) $paramList[0]; + $InfoFieldList = explode(":", $all); + + $objectClass = (string) ($InfoFieldList[0] ?? ''); + $pathToClass = (string) ($InfoFieldList[1] ?? ''); + $addCreateButton = !empty($InfoFieldList[2]) && is_numeric($InfoFieldList[2]); + $getNomUrlParam1 = 3; + $getNomUrlParam2 = ''; + if (preg_match('/#getnomurlparam1=([^#:]*)/', $all, $matches)) { + $getNomUrlParam1 = $matches[1]; + } + if (preg_match('/#getnomurlparam2=([^#:]*)/', $all, $matches)) { + $getNomUrlParam2 = $matches[1]; + } + $filter = (string) ($InfoFieldList[3] ?? ''); + $sortField = (string) ($InfoFieldList[4] ?? ''); + + return array( + 'all' => $all, + 'objectClass' => $objectClass, + 'pathToClass' => $pathToClass, + 'addCreateButton' => $addCreateButton, + 'getNomUrlParam1' => $getNomUrlParam1, + 'getNomUrlParam2' => $getNomUrlParam2, + 'filter' => $filter, + 'sortField' => $sortField, + ); + } + + /** + * Get object handler + * + * @param string $objectClass Class name + * @param string $pathToClass Path to the class + * @return CommonObject|null + */ + public function getObject($objectClass, $pathToClass) + { + dol_include_once($pathToClass); + if ($objectClass && !class_exists($objectClass)) { + // from V19 of Dolibarr, In some cases link use element instead of class, example project_task + // TODO use newObjectByElement() introduce in V20 by PR #30036 for better errors management + $element_prop = getElementProperties($objectClass); + if ($element_prop) { + $objectClass = $element_prop['classname']; + } + } + + if ($objectClass && class_exists($objectClass)) { + return new $objectClass($this->db); + } + + return null; + } +} diff --git a/htdocs/core/class/fields/multiptsfield.class.php b/htdocs/core/class/fields/multiptsfield.class.php new file mode 100644 index 00000000000..4cceca7da80 --- /dev/null +++ b/htdocs/core/class/fields/multiptsfield.class.php @@ -0,0 +1,32 @@ + + * + * 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/fields/multiptsfield.class.php + * \ingroup core + * \brief File of class to multipts field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commongeofield.class.php'; + + +/** + * Class to multipts field + */ +class MultiptsField extends CommonGeoField +{ +} diff --git a/htdocs/core/class/fields/passwordfield.class.php b/htdocs/core/class/fields/passwordfield.class.php new file mode 100644 index 00000000000..5d555fb01f5 --- /dev/null +++ b/htdocs/core/class/fields/passwordfield.class.php @@ -0,0 +1,249 @@ + + * + * 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/fields/passwordfield.class.php + * \ingroup core + * \brief File of class to password field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to password field + */ +class PasswordField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + $out = ''; // Hidden field to reduce impact of evil Google Chrome autopopulate bug. + if ($htmlName == 'pass_crypted') { + $out .= self::$form->inputType('password', 'pass', '', 'pass', $moreCss, ' autocomplete="new-password"' . $moreAttrib . $autoFocus); + $out .= self::$form->inputType('hidden', 'pass_crypted', (string) $value, 'pass_crypted', $moreCss, $moreAttrib); + } else { + $out .= self::$form->inputType('password', $htmlName, (string) $value, $htmlName, $moreCss, ' autocomplete="new-password"' . $moreAttrib . $autoFocus); + } + + return $out; + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 $langs; + + return !$this->isEmptyValue($fieldInfos, $value) ? '' . $langs->trans("Encrypted") . '' : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'maxwidth100'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + global $conf, $langs, $user; + + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + // Todo do we use other method ? + if (getDolGlobalString('USER_PASSWORD_GENERATED')) { + // Add a check on rules for password syntax using the setup of the password generator + $modGeneratePassClass = 'modGeneratePass' . ucfirst(getDolGlobalString('USER_PASSWORD_GENERATED')); + + include_once DOL_DOCUMENT_ROOT . '/core/modules/security/generate/' . $modGeneratePassClass . '.class.php'; + if (class_exists($modGeneratePassClass)) { + $modGeneratePass = new $modGeneratePassClass($this->db, $conf, $langs, $user); + '@phan-var-force ModeleGenPassword $modGeneratePass'; + + // To check an input user password, we disable the cleaning on ambiguous characters (this is used only for auto-generated password) + $modGeneratePass->WithoutAmbi = 0; + + // Call to validatePassword($password) to check pass match rules + $testpassword = $modGeneratePass->validatePassword($value); + if (!$testpassword) { + self::$validator->error = $langs->trans('RequireValidValue'); + return false; + } + } + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'password'); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + // TODO rework search on crypt password + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/phonefield.class.php b/htdocs/core/class/fields/phonefield.class.php new file mode 100644 index 00000000000..a02e6ce6252 --- /dev/null +++ b/htdocs/core/class/fields/phonefield.class.php @@ -0,0 +1,217 @@ + + * + * 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/fields/phonefield.class.php + * \ingroup core + * \brief File of class to phone field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to phone field + */ +class PhoneField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_phone((string) $value, '', 0, 0, '', ' ', 'phone') : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isPhone($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/pointfield.class.php b/htdocs/core/class/fields/pointfield.class.php new file mode 100644 index 00000000000..cecf6087507 --- /dev/null +++ b/htdocs/core/class/fields/pointfield.class.php @@ -0,0 +1,32 @@ + + * + * 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/fields/pointfield.class.php + * \ingroup core + * \brief File of class to point field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commongeofield.class.php'; + + +/** + * Class to point field + */ +class PointField extends CommonGeoField +{ +} diff --git a/htdocs/core/class/fields/polygonfield.class.php b/htdocs/core/class/fields/polygonfield.class.php new file mode 100644 index 00000000000..dd067293ea6 --- /dev/null +++ b/htdocs/core/class/fields/polygonfield.class.php @@ -0,0 +1,32 @@ + + * + * 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/fields/polygonfield.class.php + * \ingroup core + * \brief File of class to polygon field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commongeofield.class.php'; + + +/** + * Class to polygon field + */ +class PolygonField extends CommonGeoField +{ +} diff --git a/htdocs/core/class/fields/pricecyfield.class.php b/htdocs/core/class/fields/pricecyfield.class.php new file mode 100644 index 00000000000..1e64f20ffff --- /dev/null +++ b/htdocs/core/class/fields/pricecyfield.class.php @@ -0,0 +1,316 @@ + + * + * 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/fields/pricecyfield.class.php + * \ingroup core + * \brief File of class to pricecy field (price with currency) + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to pricecy field (price with currency) + */ +class PricecyField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + $tmp = $this->getPriceAndCurrencyFromValue($fieldInfos, $value); + $value = $tmp['price']; + $currency = $tmp['currency']; + + $tmp = $this->getPriceAndCurrencyAliasAndField($fieldInfos, $key); + $aliasPrice = $tmp['aliasPrice']; + $fieldPrice = $tmp['fieldPrice']; + $aliasCurrency = $tmp['aliasCurrency']; + $fieldCurrency = $tmp['fieldCurrency']; + + $out = self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + if (!empty($fieldCurrency)) { + $out .= self::$form->selectCurrency($currency, $htmlName . 'currency_id'); + } + + 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 field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + $tmp = $this->getPriceAndCurrencyFromValue($fieldInfos, $value); + $value = $tmp['price']; + $currency = $tmp['currency']; + $value = !$this->isEmptyValue($fieldInfos, $value) ? price($value) : ''; + + $out = self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib . $autoFocus); + $out .= self::$form->selectCurrency($currency, $htmlName . 'currency_id'); + + return $out; + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 $langs; + $tmp = $this->getPriceAndCurrencyFromValue($fieldInfos, $value); + $value = $tmp['price']; + $currency = $tmp['currency']; + + return !$this->isEmptyValue($fieldInfos, $value) ? price($value, 0, $langs, 0, getDolGlobalInt('MAIN_MAX_DECIMALS_TOT'), -1, $currency) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'maxwidth75'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + $tmp = $this->getPriceAndCurrencyFromValue($fieldInfos, $value); + $value = $tmp['price']; + $currency = $tmp['currency']; + if (!self::$validator->isNumeric($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml') . ':' . GETPOST($htmlName . "currency_id", 'restricthtml'); + $value = str_replace(',', '.', $value); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = price2num(GETPOST($htmlName, 'alphanohtml')) . ':' . GETPOST($htmlName . "currency_id", 'alpha'); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = array( + 'value' => GETPOST($htmlName, 'alphanohtml'), + 'currency' => GETPOST($htmlName . "currency_id", 'alpha'), + ); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + $filterValue = $value['value'] ?? ''; + $filterCurrency = $value['currency'] ?? ''; + if ($filterCurrency == '-1') $filterCurrency = ''; + + $tmp = $this->getPriceAndCurrencyAliasAndField($fieldInfos, $key); + $aliasPrice = $tmp['aliasPrice']; + $fieldPrice = $tmp['fieldPrice']; + $aliasCurrency = $tmp['aliasCurrency']; + $fieldCurrency = $tmp['fieldCurrency']; + + if (!empty($filterValue)) { + return natural_search($aliasPrice . $fieldPrice, $filterValue, 1); + } + if (!empty($filterCurrency) && !empty($fieldCurrency)) { + return natural_search($aliasCurrency . $fieldCurrency, $filterCurrency, 0); + } + + return ''; + } + + /** + * Get price and currency from value + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $value Value in memory is a php string like '0.01:EUR' + * @return array{price:double,currency:string} + */ + public function getPriceAndCurrencyFromValue($fieldInfos, $value) + { + global $conf; + + if ($this->isEmptyValue($fieldInfos, $value)) { + $price = ''; + $currency = $conf->currency; + } else { + // $value in memory is a php string like '10.01:USD' + $tmp = explode(':', $value); + $price = $this->isEmptyValue($fieldInfos, $tmp[0] ?? '') ? '' : $tmp[0]; + $currency = !empty($tmp[1]) ? $tmp[1] : $conf->currency; + } + + return array( + 'price' => (double) $price, + 'currency' => $currency + ); + } + + /** + * Get alias and field name in table for price and currency + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @return array{aliasPrice:string,aliasCurrency:string,fieldPrice:string,fieldCurrency:string} + */ + public function getPriceAndCurrencyAliasAndField($fieldInfos, $key) + { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $tmp = explode(':', $alias); + $aliasPrice = $tmp[0] ?? ''; + $aliasCurrency = $tmp[1] ?? ''; + + $field = $fieldInfos->nameInTable ?? $key; + $tmp = explode(':', $field); + $fieldPrice = $tmp[0] ?? ''; + $fieldCurrency = $tmp[1] ?? ''; + + return array( + 'aliasPrice' => trim($aliasPrice), + 'aliasCurrency' => trim($aliasCurrency), + 'fieldPrice' => trim($fieldPrice), + 'fieldCurrency' => trim($fieldCurrency), + ); + } +} diff --git a/htdocs/core/class/fields/pricefield.class.php b/htdocs/core/class/fields/pricefield.class.php new file mode 100644 index 00000000000..8d848b92348 --- /dev/null +++ b/htdocs/core/class/fields/pricefield.class.php @@ -0,0 +1,224 @@ + + * + * 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/fields/pricefield.class.php + * \ingroup core + * \brief File of class to price field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to price field + */ +class PriceField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf, $langs; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $value = !$this->isEmptyValue($fieldInfos, $value) ? price((double) $value) : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib . $autoFocus) . $langs->getCurrencySymbol($conf->currency); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 $conf, $langs; + + return !$this->isEmptyValue($fieldInfos, $value) ? price((double) $value, 0, $langs, 0, 0, -1, $conf->currency) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'maxwidth75'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isNumeric($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml'); + $value = str_replace(',', '.', $value); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = (double) price2num(GETPOST($htmlName, 'alphanohtml')); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 1); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/radiofield.class.php b/htdocs/core/class/fields/radiofield.class.php new file mode 100644 index 00000000000..ca7ee2e7af4 --- /dev/null +++ b/htdocs/core/class/fields/radiofield.class.php @@ -0,0 +1,276 @@ + + * + * 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/fields/radiofield.class.php + * \ingroup core + * \brief File of class to radio field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonselectfield.class.php'; + + +/** + * Class to radio field + */ +class RadioField extends CommonSelectField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + $value = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $optionsList = array(); + $options = $this->getOptions($fieldInfos, $key); + foreach ($options as $optionKey => $optionInfos) { + $options[$optionKey] = $optionInfos['label']; + } + + return self::$form->multiselectarray($htmlName, $optionsList, $value, 0, 0, $moreCss, 0, 0, $moreAttrib, '', '', (int) (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2'))); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + + $selectedValue = $this->isEmptyValue($fieldInfos, $value) ? '' : (string) $value; + + $options = $this->getOptions($fieldInfos, $key, true); + $values = array(); + foreach ($options as $optionKey => $optionInfos) { + $values[$optionKey] = $optionInfos['label']; + } + + return self::$form->inputRadio($htmlName, $values, $selectedValue, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 $langs; + $value = (string) $value; + + if ($fieldInfos->required && $this->isEmptyValue($fieldInfos, $value)) { + $langs->load('errors'); + $value = '' . $langs->trans('ErrorNoValueForRadioType') . ''; + } else { + $options = $this->getOptions($fieldInfos, $key); + foreach ($options as $optionKey => $optionInfos) { + if (((string) $optionKey) == $value) { + $value = $optionInfos['label']; + break; + } + } + } + + return $value; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'width25'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + global $langs; + $value = (string) $value; + + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + $options = $this->getOptions($fieldInfos, $key); + if (!isset($options[$value])) { + self::$validator->error = $langs->trans('RequireValidValue'); + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml'); + $value = trim($value); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'array'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value) && is_array($value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + + $tmp = "'" . implode("','", array_map(array($this->db, 'escape'), $value)) . "'"; + return " AND " . $field . " IN (" . $this->db->sanitize($tmp, 1) . ")"; + } + + return ''; + } + + /** + * Get list of options + * + * @param FieldInfos $fieldInfos Array of properties for field to show + * @param string $key Key of field + * @param bool $addEmptyValue Add also empty value if needed + * @param bool $reload Force reload options + * @return array + */ + public function getOptions($fieldInfos, $key, $addEmptyValue = false, $reload = false) + { + return parent::getOptions($fieldInfos, $key, $addEmptyValue, $reload); + } +} diff --git a/htdocs/core/class/fields/realfield.class.php b/htdocs/core/class/fields/realfield.class.php new file mode 100644 index 00000000000..0e3707e31fb --- /dev/null +++ b/htdocs/core/class/fields/realfield.class.php @@ -0,0 +1,220 @@ + + * + * 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/fields/realfield.class.php + * \ingroup core + * \brief File of class to real field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to real field + */ +class RealField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $value = !$this->isEmptyValue($fieldInfos, $value) ? price((double) $value) : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? price((double) $value) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'maxwidth75'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isNumeric($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml'); + $value = str_replace(',', '.', $value); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = (double) price2num(GETPOST($htmlName, 'alphanohtml')); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 1); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/selectfield.class.php b/htdocs/core/class/fields/selectfield.class.php new file mode 100644 index 00000000000..45182fbcb64 --- /dev/null +++ b/htdocs/core/class/fields/selectfield.class.php @@ -0,0 +1,271 @@ + + * + * 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/fields/selectfield.class.php + * \ingroup core + * \brief File of class to select field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonselectfield.class.php'; + + +/** + * Class to select field + */ +class SelectField extends CommonSelectField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + $value = $this->isEmptyValue($fieldInfos, $value) ? array() : (is_string($value) ? explode(',', $value) : (is_array($value) ? $value: array($value))); + + $optionsList = array(); + $options = $this->getOptions($fieldInfos, $key); + foreach ($options as $optionKey => $optionInfos) { + $options[$optionKey] = $optionInfos['label']; + } + + return self::$form->multiselectarray($htmlName, $optionsList, $value, 0, 0, $moreCss, 0, 0, $moreAttrib, '', '', (int) (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2'))); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $placeHolder = $fieldInfos->inputPlaceholder; + if (!empty($placeHolder)) $placeHolder = ' placeholder="' . dolPrintHTMLForAttribute($placeHolder) . '"'; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + $selectedValue = is_null($value) || $this->isEmptyValue($fieldInfos, $value) ? '' : (string) $value; + + $options = $this->getOptions($fieldInfos, $key, true); + + return self::$form->selectarray($htmlName, $options, $selectedValue, 0, 0, 0, $moreAttrib . $placeHolder . $autoFocus, 0, 0, 0, '', $moreCss); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + $value = (string) $value; + + if (!$this->isEmptyValue($fieldInfos, $value)) { + $options = $this->getOptions($fieldInfos, $key); + foreach ($options as $optionKey => $optionInfos) { + if (((string) $optionKey) == $value) { + $value = $optionInfos['label']; + break; + } + } + } + + return $value; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth400'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + global $langs; + $value = (string) $value; + + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + $options = $this->getOptions($fieldInfos, $key); + if (!isset($options[$value])) { + self::$validator->error = $langs->trans('RequireValidValue'); + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml'); + $value = trim($value); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } else { + $value = $defaultValue; + } + + return $this->isEmptyValue($fieldInfos, $value) ? '' : $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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'array'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value) && is_array($value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + + $tmp = "'" . implode("','", array_map(array($this->db, 'escape'), $value)) . "'"; + return " AND " . $field . " IN (" . $this->db->sanitize($tmp, 1) . ")"; + } + + return ''; + } + + /** + * Get list of options + * + * @param FieldInfos $fieldInfos Array of properties for field to show + * @param string $key Key of field + * @param bool $addEmptyValue Add also empty value if needed + * @param bool $reload Force reload options + * @return array + */ + public function getOptions($fieldInfos, $key, $addEmptyValue = false, $reload = false) + { + return parent::getOptions($fieldInfos, $key, $addEmptyValue, $reload); + } +} diff --git a/htdocs/core/class/fields/sellistfield.class.php b/htdocs/core/class/fields/sellistfield.class.php new file mode 100644 index 00000000000..61e5ab09469 --- /dev/null +++ b/htdocs/core/class/fields/sellistfield.class.php @@ -0,0 +1,298 @@ + + * + * 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/fields/sellistfield.class.php + * \ingroup core + * \brief File of class to sellist field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonsellistfield.class.php'; + + +/** + * Class to sellist field + */ +class SellistField extends CommonSellistField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array('', array()); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + + $optionsList = array(); + $options = $this->getOptions($fieldInfos, $key); + foreach ($options as $optionKey => $optionInfos) { + $options[$optionKey] = $optionInfos['label']; + } + + return self::$form->multiselectarray($htmlName, $optionsList, $value, 0, 0, $moreCss, 0, 0, $moreAttrib, '', '', (int) (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_EXTRAFIELDS_DISABLE_SELECT2'))); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $conf; + + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $placeHolder = $fieldInfos->inputPlaceholder; + if (!empty($placeHolder)) $placeHolder = ' placeholder="' . dolPrintHTMLForAttribute($placeHolder) . '"'; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + $selectedValue = is_null($value) || $this->isEmptyValue($fieldInfos, $value) ? '' : (string) $value; + + if (!empty($conf->use_javascript_ajax) && getDolGlobalString('MAIN_EXTRAFIELDS_ENABLE_NEW_SELECT2')) { + $objectId = isset($fieldInfos->otherParams['objectId']) ? (int) $fieldInfos->otherParams['objectId'] : (isset($fieldInfos->object) && is_object($fieldInfos->object) ? $fieldInfos->object->id : 0); + $objectType = isset($fieldInfos->otherParams['objectType']) ? (int) $fieldInfos->otherParams['objectType'] : (isset($fieldInfos->object) && is_object($fieldInfos->object) ? $fieldInfos->object->element : ''); + $printMode = empty($fieldInfos->mode) ? 'view' : $fieldInfos->mode; + $options = $this->getOptions($fieldInfos, $key, false, false, $selectedValue); + + // TODO add dependency support (set dependencyvalue with dependency field value get by JS) + $ajaxData = array( + 'objecttype' => $objectType, + 'objectid' => $objectId, + 'objectkey' => $key, + 'mode' => $printMode, + 'value' => $selectedValue, + 'dependencyvalue' => '', + ); + + $out = self::$form->inputSelectAjax($htmlName, $options, $selectedValue, self::$ajaxUrl, $ajaxData, $moreCss, $moreAttrib . $placeHolder . $autoFocus); + } else { + $options = $this->getOptions($fieldInfos, $key, true); + + $out = self::$form->selectarray($htmlName, $options, $selectedValue, 0, 0, 0, $moreAttrib . $placeHolder . $autoFocus, 0, 0, 0, '', $moreCss); + } + + return $out; + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + $value = (string) $value; + + if (!$this->isEmptyValue($fieldInfos, $value)) { + $options = $this->getOptions($fieldInfos, $key, false, false, $value); + if (isset($options[$value])) { + $out = $options[$value]['label']; + + $optionParams = $this->getOptionsParams($fieldInfos->options); + if ($optionParams['tableName'] == 'categorie' && !empty($optionParams['categoryType'])) { + require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php'; + $c = new Categorie($this->db); + $c->fetch((int) $value); + $color = ' style="background: #' . ($c->color ? $c->color : 'bbb') . ';"'; + $label = img_object('', 'category') . ' ' . $value; + $out = '
    • ' . $label . '
    '; + } + + $value = $out; + } + } + + return $value; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth400'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + $optionParams = $this->getOptionsParams($fieldInfos->options); + if (!self::$validator->isInDb($value, $optionParams['tableName'], $optionParams['keyField'])) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml'); + $value = trim($value); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'array'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!empty($value) && is_array($value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + + $tmp = "'" . implode("','", array_map(array($this->db, 'escape'), $value)) . "'"; + return " AND " . $field . " IN (" . $this->db->sanitize($tmp, 1) . ")"; + } + + return ''; + } + + /** + * Get list of options + * + * @param FieldInfos $fieldInfos Array of properties for field to show + * @param string $key Key of field + * @param bool $addEmptyValue Add also empty value if needed + * @param bool $reload Force reload options + * @param string|array $selectedValues Only selected values + * @return array + */ + public function getOptions($fieldInfos, $key, $addEmptyValue = false, $reload = false, $selectedValues = array()) + { + return parent::getOptions($fieldInfos, $key, $addEmptyValue, $reload, $selectedValues); + } +} diff --git a/htdocs/core/class/fields/starsfield.class.php b/htdocs/core/class/fields/starsfield.class.php new file mode 100644 index 00000000000..37187235bed --- /dev/null +++ b/htdocs/core/class/fields/starsfield.class.php @@ -0,0 +1,242 @@ + + * + * 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/fields/starsfield.class.php + * \ingroup core + * \brief File of class to stars field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to stars field + */ +class StarsField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array('', -1); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + + $size = (int) $fieldInfos->size; + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $value = (int) $value; + $htmlName = $keyPrefix . $key . $keySuffix; + + $options = array(); + for ($i = 0; $i <= $size; $i++) { + // TODO ajouter la tranduction + $options[$i] = $langs->trans("StarsFieldValue", $i); + } + + return self::$form->selectarray($htmlName, $options, $value, 1, 0, 0, $moreAttrib, 0, 0, 0, '', $moreCss); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $size = (int) $fieldInfos->size; + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $value = (int) $value; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputStars($htmlName, $size, $value, $moreCss, $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + $size = (int) $fieldInfos->size; + $value = (int) $value; + + return self::$form->outputStars($size, $value); + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $fieldInfos->minValue = 0; + $fieldInfos->maxValue = (int) $fieldInfos->size; + + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isNumeric($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + return $this->verifyFieldValue($fieldInfos, $key, GETPOST($htmlName, 'restricthtml')); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOSTINT($htmlName); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOSTINT($htmlName); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $value = (int) $value; + + if ($value >= 0) { + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), (string) $value, 1); + } + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/textfield.class.php b/htdocs/core/class/fields/textfield.class.php new file mode 100644 index 00000000000..e588de8fcf5 --- /dev/null +++ b/htdocs/core/class/fields/textfield.class.php @@ -0,0 +1,229 @@ + + * + * 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/fields/textfield.class.php + * \ingroup core + * \brief File of class to text field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; +require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php'; + + +/** + * Class to text field + */ +class TextField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputText($htmlName, (string) $value, $moreCss, $moreAttrib, $fieldInfos->options); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + if (!empty($fieldInfos->options)) { + $value = str_replace(',', "\n", (string) $value); + } + + return dol_htmlentitiesbr((string) $value); + } + + return ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + return parent::verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + $value = GETPOST($htmlName, 'restricthtml'); + + return $this->verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + if (!empty($fieldInfos->getPostCheck)) { + $value = GETPOST($htmlName, $fieldInfos->getPostCheck); + } else { + $value = GETPOST($htmlName, 'nohtml'); + if (!empty($fieldInfos->options) && !empty($fieldInfos->multiInput)) { + $tmpArrayMultiSelect = GETPOST($htmlName . '_multiselect', 'array'); + foreach ($tmpArrayMultiSelect as $tmpValue) { + $value .= (!empty($value) ? "," : "") . $tmpValue; + } + } + } + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/timestampfield.class.php b/htdocs/core/class/fields/timestampfield.class.php new file mode 100644 index 00000000000..288c9254fcc --- /dev/null +++ b/htdocs/core/class/fields/timestampfield.class.php @@ -0,0 +1,258 @@ + + * + * 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/fields/timestampfield.class.php + * \ingroup core + * \brief File of class to timestamp field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to timestamp field + */ +class TimestampField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + global $langs; + + // TODO Must also support $moreCss ? + + $htmlName = $keyPrefix . $key . $keySuffix; + $prefill = array( + 'start' => $value['start'] ?? '', + 'end' => $value['end'] ?? '', + ); + + // Search filter on a date field shows two inputs to select a date range + $out = '
    '; + $out .= self::$form->selectDate($prefill['start'], $htmlName . '_start', 1, 1, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("From"), 'tzuserrel'); + $out .= '
    '; + $out .= self::$form->selectDate($prefill['end'], $htmlName . '_end', 1, 1, 1, '', 1, 0, 0, '', '', '', '', 1, '', $langs->trans("to"), 'tzuserrel'); + $out .= '
    '; + + 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 field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $required = $fieldInfos->required ? 1 : 0; + $htmlName = $keyPrefix . $key . $keySuffix; + + // Do not show current date when field not required (see selectDate() method) + if (!$required && $this->isEmptyValue($fieldInfos, $value)) { + $value = '-1'; + } + + // TODO Must also support $moreAttrib and $moreCss ? + return self::$form->selectDate($value, $htmlName, 1, 1, $required, '', 1, 1, 0, 1, '', '', '', 1, '', '', 'tzuserrel'); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_date($value, 'dayhour', 'tzuserrel') : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss ? $defaultCss : 'minwidth200imp'); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isTimestamp($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . 'hour') || GETPOSTISSET($htmlName . 'min') || GETPOSTISSET($htmlName . 'sec') || GETPOSTISSET($htmlName . 'month') || GETPOSTISSET($htmlName . 'day') || GETPOSTISSET($htmlName . 'year')) { + $value = dol_mktime(GETPOSTINT($htmlName . 'hour'), GETPOSTINT($htmlName . 'min'), GETPOSTINT($htmlName . 'sec'), GETPOSTINT($htmlName . 'month'), GETPOSTINT($htmlName . 'day'), GETPOSTINT($htmlName . 'year'), 'tzuserrel'); // for date without hour, we use gmt + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName . '_startmonth') || GETPOSTISSET($htmlName . '_startday') || GETPOSTISSET($htmlName . '_startyear')) { + $start = dol_mktime(0, 0, 0, GETPOSTINT($htmlName . '_startmonth'), GETPOSTINT($htmlName . '_startday'), GETPOSTINT($htmlName . '_startyear'), 'tzuserrel'); + } else { + $start = is_array($defaultValue) && isset($defaultValue['start']) ? $defaultValue['start'] : ''; + } + + if (GETPOSTISSET($htmlName . '_endmonth') || GETPOSTISSET($htmlName . '_endday') || GETPOSTISSET($htmlName . '_endyear')) { + $end = dol_mktime(23, 59, 59, GETPOSTINT($htmlName . '_endmonth'), GETPOSTINT($htmlName . '_endday'), GETPOSTINT($htmlName . '_endyear'), 'tzuserrel'); + } else { + $end = is_array($defaultValue) && isset($defaultValue['end']) ? $defaultValue['end'] : ''; + } + + return array( + 'start' => $start, + 'end' => $end, + ); + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + $field = $this->db->sanitize($alias . ($fieldInfos->nameInTable ?? $key)); + $sql = ''; + + if (is_array($value)) { + $hasStart = !is_null($value['start']) && $value['start'] !== ''; + $hasEnd = !is_null($value['start']) && $value['start'] !== ''; + if ($hasStart && $hasEnd) { + $sql = " AND (" . $field . " BETWEEN '" . $this->db->idate($value['start']) . "' AND '" . $this->db->idate($value['end']) . "')"; + } elseif ($hasStart) { + $sql = " AND " . $field . " >= '" . $this->db->idate($value['start']) . "'"; + } elseif ($hasEnd) { + $sql = " AND " . $field . " <= '" . $this->db->idate($value['end']) . "'"; + } + } elseif (is_numeric($value)) { + $sql = " AND " . $field . " = '" . $this->db->idate($value) . "'"; + } + + return $sql; + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/urlfield.class.php b/htdocs/core/class/fields/urlfield.class.php new file mode 100644 index 00000000000..20040274607 --- /dev/null +++ b/htdocs/core/class/fields/urlfield.class.php @@ -0,0 +1,217 @@ + + * + * 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/fields/urlfield.class.php + * \ingroup core + * \brief File of class to url field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to url field + */ +class UrlField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_print_url((string) $value, '_blank', 32, 1) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + $result = parent::verifyFieldValue($fieldInfos, $key, $value); + if ($result && !$this->isEmptyValue($fieldInfos, $value)) { + if (!self::$validator->isUrl($value)) { + return false; + } + + $result = true; + } + + return $result; + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alphanohtml'); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fields/varcharfield.class.php b/htdocs/core/class/fields/varcharfield.class.php new file mode 100644 index 00000000000..15c4dc042a8 --- /dev/null +++ b/htdocs/core/class/fields/varcharfield.class.php @@ -0,0 +1,213 @@ + + * + * 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/fields/varcharfield.class.php + * \ingroup core + * \brief File of class to varchar field + */ + +require_once DOL_DOCUMENT_ROOT . '/core/class/fields/commonfield.class.php'; + + +/** + * Class to varchar field + */ +class VarcharField extends CommonField +{ + /** + * @var array List of value deemed as empty (null always deemed as empty) + */ + public $emptyValues = array(''); + + + /** + * Return HTML string to put an input search field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputSearchField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $moreAttrib); + } + + /** + * Return HTML string to put an input field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 printInputField($fieldInfos, $key, $value, $keyPrefix = '', $keySuffix = '', $moreCss = '', $moreAttrib = '') + { + $size = (int) $fieldInfos->size; + $maxLength = ''; // $size > 0 ? ' maxlength="' . $size . '"' : ''; // TODO rework, wrong method + $moreCss = $this->getInputCss($fieldInfos, $moreCss); + $moreAttrib = trim((string) $moreAttrib); + if (empty($moreAttrib)) $moreAttrib = ' ' . $moreAttrib; + $placeHolder = $fieldInfos->inputPlaceholder; + if (!empty($placeHolder)) $placeHolder = ' placeholder="' . dolPrintHTMLForAttribute($placeHolder) . '"'; + $autoFocus = $fieldInfos->inputAutofocus ? ' autofocus' : ''; + $htmlName = $keyPrefix . $key . $keySuffix; + + return self::$form->inputType('text', $htmlName, (string) $value, $htmlName, $moreCss, $maxLength . $moreAttrib . $placeHolder . $autoFocus); + } + + /** + * Return HTML string to show a field into a page + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 = '') + { + return !$this->isEmptyValue($fieldInfos, $value) ? dol_htmlentitiesbr((string) $value) : ''; + } + + /** + * Get input CSS + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $moreCss Value for css to define style/length of field. + * @param string $defaultCss Default value for css to define style/length of field. + * @return string + * @see self::printInputSearchField(), self::printInputField() + */ + public function getInputCss($fieldInfos, $moreCss = '', $defaultCss = '') + { + return parent::getInputCss($fieldInfos, $moreCss, $defaultCss); + } + + /** + * 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 + * @see self::printInputField() + */ + public function verifyFieldValue($fieldInfos, $key, $value) + { + return parent::verifyFieldValue($fieldInfos, $key, $value); + } + + /** + * Verify if the field value from GET/POST is valid + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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 + * @see self::printInputField() + */ + public function verifyPostFieldValue($fieldInfos, $key, $keyPrefix = '', $keySuffix = '') + { + return parent::verifyPostFieldValue($fieldInfos, $key, $keyPrefix, $keySuffix); + } + + /** + * 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 + * @see self::printInputField() + */ + public function getPostFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $check = $key == 'lang' ? 'aZ09' : 'alphanohtml'; + $value = GETPOST($htmlName, $check); + } 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 + * @see self::printInputSearchField() + */ + public function getPostSearchFieldValue($fieldInfos, $key, $defaultValue = null, $keyPrefix = '', $keySuffix = '') + { + $htmlName = $keyPrefix . $key . $keySuffix; + + if (GETPOSTISSET($htmlName)) { + $value = GETPOST($htmlName, 'alpha'); + } else { + $value = $defaultValue; + } + + return $value; + } + + /** + * Get sql filter for search field + * + * @param FieldInfos $fieldInfos Properties of the field + * @param string $key Key of field + * @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) + * @return string + * @see self::printInputSearchField(), self::getPostSearchFieldValue() + */ + public function sqlFilterSearchField($fieldInfos, $key, $value) + { + if (!$this->isEmptyValue($fieldInfos, $value)) { + $alias = $fieldInfos->sqlAlias ?? 't.'; + + return natural_search($alias . ($fieldInfos->nameInTable ?? $key), $value, 0); + } + + return ''; + } +} diff --git a/htdocs/core/class/fieldsmanager.class.php b/htdocs/core/class/fieldsmanager.class.php new file mode 100644 index 00000000000..77ca467acaa --- /dev/null +++ b/htdocs/core/class/fieldsmanager.class.php @@ -0,0 +1,1309 @@ + + * 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])) { + $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; + } +} diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 8260b5a52f3..a5a61b45e34 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -12847,4 +12847,417 @@ class Form return '
    '; } + + /** + * Html for input with label + * + * @param string $type Type of input : button, checkbox, color, email, hidden, month, number, password, radio, range, tel, text, time, url, week + * @param string $name Name + * @param string $value [=''] Value + * @param string $id [=''] Id + * @param string $morecss [=''] Class + * @param string $moreparam [=''] Add attributes (checked, required, etc) + * @param string $label [=''] Label + * @param string $addInputLabel [=''] Add label for input + * @return string Html for input with label + */ + public function inputType($type, $name, $value = '', $id = '', $morecss = '', $moreparam = '', $label = '', $addInputLabel = '') + { + $out = ''; + if ($label != '') { + $out .= '