From 21d1c9b3d2c391c8d7bfe687204e84b4a2a19fd3 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 22 Jul 2024 21:01:01 +0200 Subject: [PATCH 01/20] Fix filter on contact combo fails when 2 strings. --- htdocs/contact/ajax/contact.php | 28 +++++++++++++++++++-------- htdocs/core/class/html.form.class.php | 7 +++---- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/htdocs/contact/ajax/contact.php b/htdocs/contact/ajax/contact.php index ef0b50411b4..4a447a4ddca 100644 --- a/htdocs/contact/ajax/contact.php +++ b/htdocs/contact/ajax/contact.php @@ -128,18 +128,30 @@ if (!empty($action) && $action == 'fetch' && !empty($id)) { $prefix = getDolGlobalString('CONTACT_DONOTSEARCH_ANYWHERE') ? '' : '%'; // Can use index if CONTACT_DONOTSEARCH_ANYWHERE is on - $filter = "(lastname:like:'".$prefix.$searchkey."%') OR (firstname:like:'".$prefix.$searchkey."%')"; - if ($showsoc) { - $filter .= " OR (s.nom:like:'".$prefix.$searchkey."%')"; + $nbchar = 0; + $filter = ''; + $listofsearchkey = preg_split('/\s+/', $searchkey); + foreach ($listofsearchkey as $searchkey) { + $nbchar += strlen($searchkey); + + $filter .= ($filter ? ' AND ' : ''); + $filter .= '('; + $filter .= "(lastname:like:'".$prefix.$searchkey."%') OR (firstname:like:'".$prefix.$searchkey."%')"; + if ($showsoc) { + $filter .= " OR (s.nom:like:'".$prefix.$searchkey."%')"; + } + $filter .= ')'; } - // FIXME // If CONTACT_USE_SEARCH_TO_SELECT is set, check that nb of chars in $filter is >= to avoid DOS attack + if (getDolGlobalInt('CONTACT_USE_SEARCH_TO_SELECT') && $nbchar < getDolGlobalInt('CONTACT_USE_SEARCH_TO_SELECT')) { + print json_encode(array()); + } else { + $arrayresult = $form->selectcontacts($socid, array(), $htmlname, 1, $exclude, $limitto, $showfunction, $morecss, $options_only, $showsoc, $forcecombo, $events, $moreparam, $htmlid, $multiple, $disableifempty, $filter); - $arrayresult = $form->selectcontacts($socid, array(), $htmlname, 1, $exclude, $limitto, $showfunction, $morecss, $options_only, $showsoc, $forcecombo, $events, $moreparam, $htmlid, $multiple, $disableifempty, $filter); - - if ($outjson) { - print json_encode($arrayresult); + if ($outjson) { + print json_encode($arrayresult); + } } } diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 9a149aa2f0d..023982e6a7b 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -1746,8 +1746,7 @@ class Form * @param string $htmlid Html id to use instead of htmlname * @param bool $multiple add [] in the name of element and add 'multiple' attribute * @param integer $disableifempty Set tag 'disabled' on select if there is no choice - * @param string $filter Optional filters criteras. WARNING: To avoid SQL injection, only few chars [.a-z0-9 =<>] are allowed here, example: 's.rowid <> x' - * If you need parenthesis, use the Universal Filter Syntax, example: '(s.client:in:1,3)' + * @param string $filter Optional filters criteras. You must use the USF (Universal Search Filter) syntax, example: '(s.client:in:1,3)' * Do not use a filter coming from input of users. * @return int|string|array Return integer <0 if KO, HTML with select string if OK. */ @@ -1793,8 +1792,8 @@ class Form } } else { // If not, we do nothing. We already know that there is no parenthesis - // TODO Disallow this case in a future. - dol_syslog("Warning, select_thirdparty_list was called with a filter criteria not using the Universal Search Syntax.", LOG_WARNING); + // TODO Disallow this case in a future by returning an error here. + dol_syslog("Warning, select_thirdparty_list was called with a filter criteria not using the Universal Search Filter Syntax.", LOG_WARNING); } } From 6442a0d30c3b0acab35093153f472106dc0bad5f Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 22 Jul 2024 21:08:08 +0200 Subject: [PATCH 02/20] Fix label of category filter in thirdparty list --- htdocs/societe/list.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/htdocs/societe/list.php b/htdocs/societe/list.php index f4de2b5e4c8..fe9e3d2659a 100644 --- a/htdocs/societe/list.php +++ b/htdocs/societe/list.php @@ -1226,19 +1226,21 @@ if ($search_all) { print '
'.$langs->trans("FilterOnInto", $search_all).implode(', ', $fieldstosearchall).'
'; } -// Filter on categories $moreforfilter = ''; + +// Filter for customer categories if (empty($type) || $type == 'c' || $type == 'p') { if (isModEnabled('category') && $user->hasRight('categorie', 'read')) { $formcategory = new FormCategory($db); - $moreforfilter .= $formcategory->getFilterBox(Categorie::TYPE_CUSTOMER, $searchCategoryCustomerList, 'minwidth300', $searchCategoryCustomerOperator ? $searchCategoryCustomerOperator : 0); + $moreforfilter .= $formcategory->getFilterBox(Categorie::TYPE_CUSTOMER, $searchCategoryCustomerList, 'minwidth300', $searchCategoryCustomerOperator ? $searchCategoryCustomerOperator : 0, 1, 1, $langs->transnoentities("CustomersProspectsCategoriesShort")); } } +// Filter for supplier categories if (empty($type) || $type == 'f') { if (isModEnabled("fournisseur") && isModEnabled('category') && $user->hasRight('categorie', 'read')) { $formcategory = new FormCategory($db); - $moreforfilter .= $formcategory->getFilterBox(Categorie::TYPE_SUPPLIER, $searchCategorySupplierList, 'minwidth300', $searchCategorySupplierOperator ? $searchCategorySupplierOperator : 0); + $moreforfilter .= $formcategory->getFilterBox(Categorie::TYPE_SUPPLIER, $searchCategorySupplierList, 'minwidth300', $searchCategorySupplierOperator ? $searchCategorySupplierOperator : 0, 1, 1, $langs->transnoentities("SuppliersCategoriesShort")); } } From 403fcd234ed7884a993efa70a108e196cc87fdbf Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 22 Jul 2024 21:49:47 +0200 Subject: [PATCH 03/20] Debug v20 --- htdocs/admin/mails_templates.php | 168 ++++++++++++---------- htdocs/core/class/html.formmail.class.php | 7 +- htdocs/core/tpl/card_presend.tpl.php | 3 +- htdocs/langs/en_US/admin.lang | 1 + 4 files changed, 98 insertions(+), 81 deletions(-) diff --git a/htdocs/admin/mails_templates.php b/htdocs/admin/mails_templates.php index 56126f2f56b..22d9ddab704 100644 --- a/htdocs/admin/mails_templates.php +++ b/htdocs/admin/mails_templates.php @@ -252,6 +252,8 @@ if ($reshook == 0) { } } +$error = 0; + $id = 25; $acceptlocallinktomedia = (acceptLocalLinktoMedia() > 0 ? 1 : 0); @@ -436,82 +438,94 @@ if (empty($reshook)) { if ($ok && GETPOST('actionmodify')) { $rowidcol = "rowid"; - // Modify entry - $sql = "UPDATE ".$tabname[$id]." SET "; - // Modify value of fields - $i = 0; - foreach ($listfieldmodify as $field) { - if ($field == 'entity') { - // entity not present on listfieldmodify array - $keycode = $field; - $_POST[$keycode] = $conf->entity; - } else { - $keycode = $listfieldvalue[$i]; - } - - if ($field == 'lang') { - $keycode = 'langcode'; - } - if (empty($keycode)) { - $keycode = $field; - } - - // Rename some POST variables into a generic name - if ($field == 'fk_user' && !(GETPOSTINT('fk_user') > 0)) { - $_POST['fk_user'] = ''; - } - if ($field == 'topic') { - $_POST['topic'] = GETPOST('topic-'.$rowid); - } - if ($field == 'joinfiles') { - $_POST['joinfiles'] = GETPOST('joinfiles-'.$rowid); - } - if ($field == 'content') { - $_POST['content'] = GETPOST('content-'.$rowid, 'restricthtml'); - } - if ($field == 'content_lines') { - $_POST['content_lines'] = GETPOST('content_lines-'.$rowid, 'restricthtml'); - } - - if ($i) { - $sql .= ", "; - } - $sql .= $field."="; - - if (GETPOST($keycode) == '' || (!in_array($keycode, array('langcode', 'position', 'private', 'defaultfortype')) && !GETPOST($keycode))) { - $sql .= "null"; // langcode,... must be '' if not defined so the unique key that include lang will work - } elseif (GETPOST($keycode) == '0' && $keycode == 'langcode') { - $sql .= "''"; // langcode must be '' if not defined so the unique key that include lang will work - } elseif ($keycode == 'fk_user') { - if (!$user->admin) { // A non admin user can only edit its own template - $sql .= " ".((int) $user->id); - } else { - $sql .= " ".(GETPOSTINT($keycode)); - } - } elseif ($keycode == 'content') { - $sql .= "'".$db->escape(GETPOST($keycode, 'restricthtml'))."'"; - } elseif (in_array($keycode, array('joinfiles', 'defaultfortype', 'private', 'position'))) { - $sql .= GETPOSTINT($keycode); - } else { - $sql .= "'".$db->escape(GETPOST($keycode, 'alphanohtml'))."'"; - } - $i++; - } - - $sql .= " WHERE ".$db->escape($rowidcol)." = ".((int) $rowid); - if (!$user->admin) { // A non admin user can only edit its own template - $sql .= " AND fk_user = ".((int) $user->id); - } - //print $sql;exit; - dol_syslog("actionmodify", LOG_DEBUG); - //print $sql; - $resql = $db->query($sql); - if ($resql) { - setEventMessages($langs->transnoentities("RecordSaved"), null, 'mesgs'); - } else { - setEventMessages($db->error(), null, 'errors'); + if (GETPOSTINT('fk_user') <= 0 && GETPOST('private')) { + setEventMessages($langs->trans("AnOwnerMustBeSetIfEmailTemplateIsPrivate"), null, 'errors'); + $error++; $action = 'edit'; } + + if (!$error) { + // Modify entry + $sql = "UPDATE ".$tabname[$id]." SET "; + // Modify value of fields + $i = 0; + foreach ($listfieldmodify as $field) { + if ($field == 'entity') { + // entity not present on listfieldmodify array + $keycode = $field; + $_POST[$keycode] = $conf->entity; + } else { + $keycode = $listfieldvalue[$i]; + } + + if ($field == 'lang') { + $keycode = 'langcode'; + } + if (empty($keycode)) { + $keycode = $field; + } + + // Rename some POST variables into a generic name + if ($field == 'fk_user' && !(GETPOSTINT('fk_user') > 0)) { + $_POST['fk_user'] = ''; + } + if ($field == 'topic') { + $_POST['topic'] = GETPOST('topic-'.$rowid); + } + if ($field == 'joinfiles') { + $_POST['joinfiles'] = GETPOST('joinfiles-'.$rowid); + } + if ($field == 'content') { + $_POST['content'] = GETPOST('content-'.$rowid, 'restricthtml'); + } + if ($field == 'content_lines') { + $_POST['content_lines'] = GETPOST('content_lines-'.$rowid, 'restricthtml'); + } + + if ($i) { + $sql .= ", "; + } + $sql .= $field."="; + + if (GETPOST($keycode) == '' || (!in_array($keycode, array('langcode', 'position', 'private', 'defaultfortype')) && !GETPOST($keycode))) { + $sql .= "null"; // langcode,... must be '' if not defined so the unique key that include lang will work + } elseif (GETPOST($keycode) == '0' && $keycode == 'langcode') { + $sql .= "''"; // langcode must be '' if not defined so the unique key that include lang will work + } elseif ($keycode == 'fk_user') { + if (!$user->admin) { // A non admin user can only edit its own template + $sql .= " ".((int) $user->id); + } else { + $sql .= " ".(GETPOSTINT($keycode)); + } + } elseif ($keycode == 'content') { + $sql .= "'".$db->escape(GETPOST($keycode, 'restricthtml'))."'"; + } elseif (in_array($keycode, array('joinfiles', 'defaultfortype', 'private', 'position'))) { + $sql .= GETPOSTINT($keycode); + } else { + $sql .= "'".$db->escape(GETPOST($keycode, 'alphanohtml'))."'"; + } + $i++; + } + + $sql .= " WHERE ".$db->escape($rowidcol)." = ".((int) $rowid); + if (!$user->admin) { // A non admin user can only edit its own template + $sql .= " AND fk_user = ".((int) $user->id); + } + //print $sql;exit; + dol_syslog("actionmodify", LOG_DEBUG); + + //print $sql; + $resql = $db->query($sql); + if (!$resql) { + $error++; + setEventMessages($db->error(), null, 'errors'); + $action = 'edit'; + } + } + + if (!$error) { + setEventMessages($langs->transnoentities("RecordSaved"), null, 'mesgs'); + } } } @@ -1380,7 +1394,7 @@ function fieldList($fieldlist, $obj = null, $tabname = '', $context = '') } elseif ($value == 'fk_user') { print ''; if ($user->admin && $context != 'preview') { - print $form->select_dolusers(empty($obj->$value) ? '' : $obj->$value, 'fk_user', 1, null, 0, ($user->admin ? '' : 'hierarchyme'), null, 0, 0, 0, '', 0, '', 'minwidth75 maxwidth100'); + print $form->select_dolusers(GETPOSTISSET('fk_user') ? GETPOSTINT('fk_user') : (empty($obj->$value) ? '' : $obj->$value), 'fk_user', 1, null, 0, ($user->admin ? '' : 'hierarchyme'), null, 0, 0, 0, '', 0, '', 'minwidth75 maxwidth100'); } else { if ($context == 'add') { // I am not admin and we show the add form print $user->getNomUrl(-1); // Me @@ -1467,10 +1481,10 @@ function fieldList($fieldlist, $obj = null, $tabname = '', $context = '') if ($value == 'private' && $context != 'preview') { if (empty($user->admin)) { // @phan-suppress-next-line PhanPluginSuspiciousParamPosition - print $form->selectyesno($value, '1', 1); + print $form->selectyesno($value, '1', 1, false, 0, 1); } else { // @phan-suppress-next-line PhanPluginSuspiciousParamPosition - print $form->selectyesno($value, (isset($obj->$value) ? $obj->$value : ''), 1); + print $form->selectyesno($value, (isset($obj->$value) ? $obj->$value : ''), 1, false, 0, 1); } } else { print ''; diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index 49aec430b3f..633f78fc182 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -499,7 +499,7 @@ class FormMail extends Form $modelmail_array = array(); if ($this->param['models'] != 'none') { - $result = $this->fetchAllEMailTemplate($this->param["models"], $user, $outputlangs); + $result = $this->fetchAllEMailTemplate($this->param["models"], $user, $outputlangs); // Fill $this->lines_model if ($result < 0) { setEventMessages($this->error, $this->errors, 'errors'); } @@ -1604,7 +1604,7 @@ class FormMail extends Form $languagetosearchmain = ''; } - $sql = "SELECT rowid, module, label, type_template, topic, email_from, joinfiles, content, content_lines, lang, email_from, email_to, email_tocc, email_tobcc"; + $sql = "SELECT rowid, entity, module, label, type_template, topic, email_from, joinfiles, content, content_lines, lang, email_from, email_to, email_tocc, email_tobcc"; $sql .= " FROM ".$dbs->prefix().'c_email_templates'; $sql .= " WHERE (type_template = '".$dbs->escape($type_template)."' OR type_template = 'all')"; $sql .= " AND entity IN (".getEntity('c_email_templates').")"; @@ -1627,6 +1627,7 @@ class FormMail extends Form if ($id == -1) { $sql .= " AND position = 0"; } + $sql .= " AND entity IN(".getEntity('c_email_templates', 1).")"; if ($languagetosearch) { $sql .= $dbs->order("position,lang,label", "ASC,DESC,ASC"); // We want line with lang set first, then with lang null or '' } else { @@ -1753,7 +1754,7 @@ class FormMail extends Form } /** - * Find if template exists and are available for current user, then set them into $this->lines_module. + * Find if template exists and are available for current user, then set them into $this->lines_model. * Search into table c_email_templates * * @param string $type_template Get message for key module diff --git a/htdocs/core/tpl/card_presend.tpl.php b/htdocs/core/tpl/card_presend.tpl.php index 53313f06e90..6731ceb67e4 100644 --- a/htdocs/core/tpl/card_presend.tpl.php +++ b/htdocs/core/tpl/card_presend.tpl.php @@ -27,6 +27,7 @@ * $defaulttopic and $defaulttopiclang * $diroutput * $arrayoffamiliestoexclude=array('system', 'mycompany', 'object', 'objectamount', 'date', 'user', ...); + * $file */ // Protection to avoid direct call of template @@ -134,7 +135,7 @@ if ($action == 'presend') { $formmail = new FormMail($db); $formmail->param['langsmodels'] = (empty($newlang) ? $langs->defaultlang : $newlang); - $formmail->fromtype = (GETPOST('fromtype') ? GETPOST('fromtype') : (getDolGlobalString('MAIN_MAIL_DEFAULT_FROMTYPE') ? $conf->global->MAIN_MAIL_DEFAULT_FROMTYPE : 'user')); + $formmail->fromtype = (GETPOST('fromtype') ? GETPOST('fromtype') : getDolGlobalString('MAIN_MAIL_DEFAULT_FROMTYPE', 'user')); if ($formmail->fromtype === 'user') { $formmail->fromid = $user->id; diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index 363935d4194..93c3eceb2c4 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -2509,3 +2509,4 @@ SendToUrl=Send to Url WebsiteTemplateWasCopied=The website template(s) "%s" provided by this module has been saved into the directory of website templates (/doctemplates/websites) and is ready to be imported as a new web site. EnabledByDefaultAtInstall=Enabled by default at install VulnerableToRCEAttack=You are vulnerable to RCE attacks by using the custom dol_json_decode function +AnOwnerMustBeSetIfEmailTemplateIsPrivate=An owner must be set if the email template is set as private From 8a257ac93f11c7f87161d1f5d7a1bc252f71aa9e Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 23 Jul 2024 02:12:46 +0200 Subject: [PATCH 04/20] Fix permission to edit email template - Debug v20 --- htdocs/admin/mails_templates.php | 111 ++++++++-------- htdocs/core/class/html.formmail.class.php | 121 +++++++++++++++++- .../template/class/myobject.class.php | 8 +- 3 files changed, 179 insertions(+), 61 deletions(-) diff --git a/htdocs/admin/mails_templates.php b/htdocs/admin/mails_templates.php index 22d9ddab704..d587ef016ff 100644 --- a/htdocs/admin/mails_templates.php +++ b/htdocs/admin/mails_templates.php @@ -37,11 +37,11 @@ require '../main.inc.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php'; // Load translation files required by the page $langsArray = array("errors", "admin", "mails", "languages"); @@ -62,8 +62,7 @@ $confirm = GETPOST('confirm', 'alpha'); // Result of a confirmation $mode = GETPOST('mode', 'aZ09'); $optioncss = GETPOST('optioncss', 'alpha'); -$id = GETPOSTINT('id'); -$rowid = GETPOST('rowid', 'alpha'); +$id = $rowid = (GETPOSTINT('id') ? GETPOSTINT('id') : GETPOSTINT('rowid')); $search_label = GETPOST('search_label', 'alphanohtml'); // Must allow value like 'Abc Def' or '(MyTemplateName)' $search_type_template = GETPOST('search_type_template', 'alpha'); $search_lang = GETPOST('search_lang', 'alpha'); @@ -254,8 +253,6 @@ if ($reshook == 0) { $error = 0; -$id = 25; - $acceptlocallinktomedia = (acceptLocalLinktoMedia() > 0 ? 1 : 0); // Security @@ -264,8 +261,16 @@ if (!empty($user->socid)) { } $permissiontoadd = 1; -$permissiontodelete = 1; - +$permissiontoedit = ($user->admin ? 1 : 0); +$permissiontodelete = ($user->admin ? 1 : 0); +if ($rowid > 0) { + $tmpmailtemplate = new ModelMail($db); + $tmpmailtemplate->fetch($rowid); + if ($tmpmailtemplate->fk_user == $user->id) { + $permissiontoedit = 1; + $permissiontodelete = 1; + } +} /* @@ -304,11 +309,11 @@ if (empty($reshook)) { } // Actions add or modify an email template - if ((GETPOST('actionadd', 'alpha') || GETPOST('actionmodify', 'alpha')) && $permissiontoadd) { - $listfield = explode(',', str_replace(' ', '', $tabfield[$id])); - $listfieldinsert = explode(',', $tabfieldinsert[$id]); - $listfieldmodify = explode(',', $tabfieldinsert[$id]); - $listfieldvalue = explode(',', $tabfieldvalue[$id]); + if ((GETPOST('actionadd', 'alpha') && $permissiontoadd) || (GETPOST('actionmodify', 'alpha') && $permissiontoedit)) { + $listfield = explode(',', str_replace(' ', '', $tabfield[25])); + $listfieldinsert = explode(',', $tabfieldinsert[25]); + $listfieldmodify = explode(',', $tabfieldinsert[25]); + $listfieldvalue = explode(',', $tabfieldvalue[25]); // Check that all fields are filled $ok = 1; @@ -360,9 +365,9 @@ if (empty($reshook)) { // If previous test is ok action is add, we add the line if ($ok && GETPOST('actionadd')) { // Add new entry - $sql = "INSERT INTO ".$tabname[$id]." ("; + $sql = "INSERT INTO ".$tabname[25]." ("; // List of fields - $sql .= $tabfieldinsert[$id]; + $sql .= $tabfieldinsert[25]; $sql .= ", active, enabled)"; $sql .= " VALUES("; @@ -423,7 +428,7 @@ if (empty($reshook)) { $result = $db->query($sql); if ($result) { // Add is ok setEventMessages($langs->transnoentities("RecordSaved"), null, 'mesgs'); - $_POST = array('id' => $id); // Clean $_POST array, we keep only id + $_POST = array('id' => 25); // Clean $_POST array, we keep only id } else { if ($db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') { setEventMessages($langs->transnoentities("ErrorRecordAlreadyExists"), null, 'errors'); @@ -446,7 +451,7 @@ if (empty($reshook)) { if (!$error) { // Modify entry - $sql = "UPDATE ".$tabname[$id]." SET "; + $sql = "UPDATE ".$tabname[25]." SET "; // Modify value of fields $i = 0; foreach ($listfieldmodify as $field) { @@ -532,7 +537,7 @@ if (empty($reshook)) { if ($action == 'confirm_delete' && $confirm == 'yes' && $permissiontodelete) { // delete $rowidcol = "rowid"; - $sql = "DELETE from ".$tabname[$id]." WHERE ".$rowidcol." = ".((int) $rowid); + $sql = "DELETE from ".$tabname[25]." WHERE ".$rowidcol." = ".((int) $rowid); if (!$user->admin) { // A non admin user can only edit its own template $sql .= " AND fk_user = ".((int) $user->id); } @@ -548,10 +553,10 @@ if (empty($reshook)) { } // activate - if ($action == $acts[0] && $permissiontoadd) { + if ($action == $acts[0] && $permissiontoedit) { $rowidcol = "rowid"; - $sql = "UPDATE ".$tabname[$id]." SET active = 1 WHERE rowid = ".((int) $rowid); + $sql = "UPDATE ".$tabname[25]." SET active = 1 WHERE rowid = ".((int) $rowid); $result = $db->query($sql); if (!$result) { @@ -560,10 +565,10 @@ if (empty($reshook)) { } // disable - if ($action == $acts[1] && $permissiontoadd) { + if ($action == $acts[1] && $permissiontoedit) { $rowidcol = "rowid"; - $sql = "UPDATE ".$tabname[$id]." SET active = 0 WHERE rowid = ".((int) $rowid); + $sql = "UPDATE ".$tabname[25]." SET active = 0 WHERE rowid = ".((int) $rowid); $result = $db->query($sql); if (!$result) { @@ -578,11 +583,10 @@ if (empty($reshook)) { */ $form = new Form($db); +$formadmin = new FormAdmin($db); $now = dol_now(); -$formadmin = new FormAdmin($db); - //$help_url = "EN:Module_MyObject|FR:Module_MyObject_FR|ES:Módulo_MyObject"; $help_url = ''; if (!empty($user->admin) && (empty($_SESSION['leftmenu']) || $_SESSION['leftmenu'] != 'email_templates')) { @@ -698,11 +702,11 @@ if (!empty($user->admin) && (empty($_SESSION['leftmenu']) || $_SESSION['leftmenu // Confirm deletion of record if ($action == 'delete') { - print $form->formconfirm($_SERVER["PHP_SELF"].'?'.($page ? 'page='.$page.'&' : '').'sortfield='.$sortfield.'&sortorder='.$sortorder.'&rowid='.((int) $rowid).'&id='.((int) $id), $langs->trans('DeleteLine'), $langs->trans('ConfirmDeleteLine'), 'confirm_delete', '', 0, 1); + print $form->formconfirm($_SERVER["PHP_SELF"].'?'.($page ? 'page='.$page.'&' : '').'sortfield='.$sortfield.'&sortorder='.$sortorder.'&rowid='.((int) $rowid), $langs->trans('DeleteLine'), $langs->trans('ConfirmDeleteLine'), 'confirm_delete', '', 0, 1); } -$fieldlist = explode(',', $tabfield[$id]); +$fieldlist = explode(',', $tabfield[25]); if ($action == 'create') { // If data was already input, we define them in obj to populate input fields. @@ -719,7 +723,7 @@ if ($action == 'create') { $obj->content = GETPOST('content', 'restricthtml'); // Form to add a new line - print '
'; + print ''; print ''; print ''; print ''; @@ -778,13 +782,13 @@ if ($action == 'create') { } if ($valuetoshow != '') { print ''; - if (!empty($tabhelp[$id][$value]) && preg_match('/^http(s*):/i', $tabhelp[$id][$value])) { - print ''.$valuetoshow.' '.img_help(1, $valuetoshow).''; - } elseif (!empty($tabhelp[$id][$value])) { + if (!empty($tabhelp[25][$value]) && preg_match('/^http(s*):/i', $tabhelp[25][$value])) { + print ''.$valuetoshow.' '.img_help(1, $valuetoshow).''; + } elseif (!empty($tabhelp[25][$value])) { if (in_array($value, array('topic'))) { - print $form->textwithpicto($valuetoshow, $tabhelp[$id][$value], 1, 'help', '', 0, 2, $value); // Tooltip on click + print $form->textwithpicto($valuetoshow, $tabhelp[25][$value], 1, 'help', '', 0, 2, $value); // Tooltip on click } else { - print $form->textwithpicto($valuetoshow, $tabhelp[$id][$value], 1, 'help', '', 0, 2); // Tooltip on hover + print $form->textwithpicto($valuetoshow, $tabhelp[25][$value], 1, 'help', '', 0, 2); // Tooltip on hover } } else { print $valuetoshow; @@ -793,14 +797,13 @@ if ($action == 'create') { } } print ''; - print ''; print ''; print ''; $tmpaction = 'create'; $parameters = array( 'fieldlist' => $fieldlist, - 'tabname' => $tabname[$id] + 'tabname' => $tabname[25] ); $reshook = $hookmanager->executeHooks('createEmailTemplateFieldlist', $parameters, $obj, $tmpaction); // Note that $action and $object may have been modified by some hooks $error = $hookmanager->error; @@ -812,9 +815,9 @@ if ($action == 'create') { if (empty($reshook)) { if ($action == 'edit') { - fieldList($fieldlist, $obj, $tabname[$id], 'hide'); + fieldList($fieldlist, $obj, $tabname[25], 'hide'); } else { - fieldList($fieldlist, $obj, $tabname[$id], 'add'); + fieldList($fieldlist, $obj, $tabname[25], 'add'); } } // Action column @@ -832,19 +835,19 @@ if ($action == 'create') { foreach ($fieldsforcontent as $tmpfieldlist) { // Topic of email if ($tmpfieldlist == 'topic') { - print ''.$form->textwithpicto($langs->trans("Topic"), $tabhelp[$id][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).' '; + print ''.$form->textwithpicto($langs->trans("Topic"), $tabhelp[25][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).' '; } if ($tmpfieldlist == 'email_from') { - print $form->textwithpicto($langs->trans("MailFrom"), $tabhelp[$id][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist); + print $form->textwithpicto($langs->trans("MailFrom"), $tabhelp[25][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist); } if ($tmpfieldlist == 'joinfiles') { - print ''.$form->textwithpicto($langs->trans("FilesAttachedToEmail"), $tabhelp[$id][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).' '; + print ''.$form->textwithpicto($langs->trans("FilesAttachedToEmail"), $tabhelp[25][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).' '; } if ($tmpfieldlist == 'content') { - print $form->textwithpicto($langs->trans("Content"), $tabhelp[$id][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).'
'; + print $form->textwithpicto($langs->trans("Content"), $tabhelp[25][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).'
'; } if ($tmpfieldlist == 'content_lines') { - print $form->textwithpicto($langs->trans("ContentForLines"), $tabhelp[$id][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).'
'; + print $form->textwithpicto($langs->trans("ContentForLines"), $tabhelp[25][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).'
'; } // Input field @@ -892,7 +895,7 @@ if (!$resql) { $num = $db->num_rows($resql); -print ''; +print ''; print ''; print ''; @@ -901,7 +904,7 @@ print ''; $i = 0; -$param = '&id='.((int) $id); +$param = ''; if ($search_label) { $param .= '&search_label='.urlencode($search_label); } @@ -1050,11 +1053,11 @@ foreach ($fieldlist as $field => $value) { // Show fields if ($showfield) { - if (!empty($tabhelp[$id][$value])) { + if (!empty($tabhelp[25][$value])) { if (in_array($value, array('topic'))) { - $valuetoshow = $form->textwithpicto($valuetoshow, $tabhelp[$id][$value], 1, 'help', '', 0, 2, 'tooltip'.$value, $forcenowrap); // Tooltip on click + $valuetoshow = $form->textwithpicto($valuetoshow, $tabhelp[25][$value], 1, 'help', '', 0, 2, 'tooltip'.$value, $forcenowrap); // Tooltip on click } else { - $valuetoshow = $form->textwithpicto($valuetoshow, $tabhelp[$id][$value], 1, 'help', '', 0, 2, '', $forcenowrap); // Tooltip on hover + $valuetoshow = $form->textwithpicto($valuetoshow, $tabhelp[25][$value], 1, 'help', '', 0, 2, '', $forcenowrap); // Tooltip on hover } } $sortfieldtouse = ($sortable ? $fieldlist[$field] : ''); @@ -1084,7 +1087,7 @@ if ($num) { print ''; $tmpaction = 'edit'; - $parameters = array('fieldlist' => $fieldlist, 'tabname' => $tabname[$id]); + $parameters = array('fieldlist' => $fieldlist, 'tabname' => $tabname[25]); $reshook = $hookmanager->executeHooks('editEmailTemplateFieldlist', $parameters, $obj, $tmpaction); // Note that $action and $object may have been modified by some hooks $error = $hookmanager->error; $errors = $hookmanager->errors; @@ -1103,7 +1106,7 @@ if ($num) { } // Show main fields if (empty($reshook)) { - fieldList($fieldlist, $obj, $tabname[$id], $action); + fieldList($fieldlist, $obj, $tabname[25], $action); } // Action column if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { @@ -1136,23 +1139,23 @@ if ($num) { if ($showfield) { // Show line for topic, joinfiles and content if ($tmpfieldlist == 'topic') { - print '
'.$form->textwithpicto($langs->trans("Topic"), $tabhelp[$id][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).'
'; + print '
'.$form->textwithpicto($langs->trans("Topic"), $tabhelp[25][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).'
'; print ''; print '
'."\n"; } if ($tmpfieldlist == 'email_from') { - print '
'.$form->textwithpicto($langs->trans("MailFrom"), $tabhelp[$id][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).'
'; + print '
'.$form->textwithpicto($langs->trans("MailFrom"), $tabhelp[25][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).'
'; print ''; print '
'."\n"; } if ($tmpfieldlist == 'joinfiles') { - print '
'.$form->textwithpicto($langs->trans("FilesAttachedToEmail"), $tabhelp[$id][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).'
'; + print '
'.$form->textwithpicto($langs->trans("FilesAttachedToEmail"), $tabhelp[25][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).'
'; print $form->selectyesno($tmpfieldlist.'-'.$rowid, (isset($obj->$tmpfieldlist) ? $obj->$tmpfieldlist : '0'), 1, ($action != 'edit'), 0, 1); print '
'."\n"; } if ($tmpfieldlist == 'content') { - print $form->textwithpicto($langs->trans("Content"), $tabhelp[$id][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).'
'; + print $form->textwithpicto($langs->trans("Content"), $tabhelp[25][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).'
'; $okforextended = true; if (!getDolGlobalString('FCKEDITOR_ENABLE_MAIL')) { $okforextended = false; @@ -1162,7 +1165,7 @@ if ($num) { } if ($tmpfieldlist == 'content_lines') { print '
'."\n"; - print $form->textwithpicto($langs->trans("ContentForLines"), $tabhelp[$id][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).'
'; + print $form->textwithpicto($langs->trans("ContentForLines"), $tabhelp[25][$tmpfieldlist], 1, 'help', '', 0, 2, $tmpfieldlist).'
'; $okforextended = true; if (!getDolGlobalString('FCKEDITOR_ENABLE_MAIL')) { $okforextended = false; @@ -1235,7 +1238,7 @@ if ($num) { } $tmpaction = 'view'; - $parameters = array('fieldlist' => $fieldlist, 'tabname' => $tabname[$id]); + $parameters = array('fieldlist' => $fieldlist, 'tabname' => $tabname[25]); $reshook = $hookmanager->executeHooks('viewEmailTemplateFieldlist', $parameters, $obj, $tmpaction); // Note that $action and $object may have been modified by some hooks $error = $hookmanager->error; @@ -1481,7 +1484,7 @@ function fieldList($fieldlist, $obj = null, $tabname = '', $context = '') if ($value == 'private' && $context != 'preview') { if (empty($user->admin)) { // @phan-suppress-next-line PhanPluginSuspiciousParamPosition - print $form->selectyesno($value, '1', 1, false, 0, 1); + print $form->selectyesno($value, GETPOSTISSET($value) ? GETPOSTINT($value) : (($context != 'add' && isset($obj->$value)) ? $obj->$value : '1'), 1, false, 0, 1); } else { // @phan-suppress-next-line PhanPluginSuspiciousParamPosition print $form->selectyesno($value, (isset($obj->$value) ? $obj->$value : ''), 1, false, 0, 1); diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index 633f78fc182..5c4715e9afb 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -1990,13 +1990,61 @@ class FormMail extends Form } +require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; + /** - * ModelMail - * * Object of table llx_c_email_templates + * + * TODO Move this class into a file cemailtemplate.class.php */ -class ModelMail +class ModelMail extends CommonObject { + /** + * @var string ID to identify managed object. + */ + public $element = 'email_template'; + + /** + * @var string Name of table without prefix where object is stored. This is also the key used for extrafields management (so extrafields know the link to the parent table). + */ + public $table_element = 'c_email_templates'; + + + // BEGIN MODULEBUILDER PROPERTIES + /** + * @var array Array with all fields and their property. Do not use it as a static var. It may be modified by constructor. + */ + public $fields=array( + "rowid" => array("type"=>"integer", "label"=>"TechnicalID", "enabled"=>"1", 'position'=>10, 'notnull'=>1, "visible"=>"-1",), + "module" => array("type"=>"varchar(32)", "label"=>"Module", "enabled"=>"1", 'position'=>20, 'notnull'=>0, "visible"=>"-1",), + "type_template" => array("type"=>"varchar(32)", "label"=>"Typetemplate", "enabled"=>"1", 'position'=>25, 'notnull'=>0, "visible"=>"-1",), + "lang" => array("type"=>"varchar(6)", "label"=>"Lang", "enabled"=>"1", 'position'=>30, 'notnull'=>0, "visible"=>"-1",), + "private" => array("type"=>"smallint(6)", "label"=>"Private", "enabled"=>"1", 'position'=>35, 'notnull'=>1, "visible"=>"-1",), + "fk_user" => array("type"=>"integer:User:user/class/user.class.php", "label"=>"Fkuser", "enabled"=>"1", 'position'=>40, 'notnull'=>0, "visible"=>"-1", "css"=>"maxwidth500 widthcentpercentminusxx", "csslist"=>"tdoverflowmax150",), + "datec" => array("type"=>"datetime", "label"=>"DateCreation", "enabled"=>"1", 'position'=>45, 'notnull'=>0, "visible"=>"-1",), + "tms" => array("type"=>"timestamp", "label"=>"DateModification", "enabled"=>"1", 'position'=>50, 'notnull'=>1, "visible"=>"-1",), + "label" => array("type"=>"varchar(255)", "label"=>"Label", "enabled"=>"1", 'position'=>55, 'notnull'=>0, "visible"=>"-1", "alwayseditable"=>"1", "css"=>"minwidth300", "cssview"=>"wordbreak", "csslist"=>"tdoverflowmax150",), + "position" => array("type"=>"smallint(6)", "label"=>"Position", "enabled"=>"1", 'position'=>60, 'notnull'=>0, "visible"=>"-1", "alwayseditable"=>"1",), + "active" => array("type"=>"integer", "label"=>"Active", "enabled"=>"1", 'position'=>65, 'notnull'=>1, "visible"=>"-1", "alwayseditable"=>"1",), + "topic" => array("type"=>"text", "label"=>"Topic", "enabled"=>"1", 'position'=>70, 'notnull'=>0, "visible"=>"-1", "alwayseditable"=>"1",), + "content" => array("type"=>"mediumtext", "label"=>"Content", "enabled"=>"1", 'position'=>75, 'notnull'=>0, "visible"=>"-1", "alwayseditable"=>"1",), + "content_lines" => array("type"=>"text", "label"=>"Contentlines", "enabled"=>"1", 'position'=>80, 'notnull'=>0, "visible"=>"-1", "alwayseditable"=>"1",), + "enabled" => array("type"=>"varchar(255)", "label"=>"Enabled", "enabled"=>"1", 'position'=>85, 'notnull'=>0, "visible"=>"-1", "alwayseditable"=>"1",), + "joinfiles" => array("type"=>"varchar(255)", "label"=>"Joinfiles", "enabled"=>"1", 'position'=>90, 'notnull'=>0, "visible"=>"-1", "alwayseditable"=>"1",), + "email_from" => array("type"=>"varchar(255)", "label"=>"Emailfrom", "enabled"=>"1", 'position'=>95, 'notnull'=>0, "visible"=>"-1", "alwayseditable"=>"1",), + "email_to" => array("type"=>"varchar(255)", "label"=>"Emailto", "enabled"=>"1", 'position'=>100, 'notnull'=>0, "visible"=>"-1", "alwayseditable"=>"1",), + "email_tocc" => array("type"=>"varchar(255)", "label"=>"Emailtocc", "enabled"=>"1", 'position'=>105, 'notnull'=>0, "visible"=>"-1", "alwayseditable"=>"1",), + "email_tobcc" => array("type"=>"varchar(255)", "label"=>"Emailtobcc", "enabled"=>"1", 'position'=>110, 'notnull'=>0, "visible"=>"-1", "alwayseditable"=>"1",), + "defaultfortype" => array("type"=>"smallint(6)", "label"=>"Defaultfortype", "enabled"=>"1", 'position'=>115, 'notnull'=>0, "visible"=>"-1", "alwayseditable"=>"1",), + ); + public $rowid; + public $type_template; + public $datec; + public $tms; + public $active; + public $enabled; + public $defaultfortype; + /** * @var int ID */ @@ -2044,4 +2092,71 @@ class ModelMail * @var int Position of template in a combo list */ public $position; + // END MODULEBUILDER PROPERTIES + + + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + public function __construct(DoliDB $db) + { + global $langs; + + $this->db = $db; + $this->ismultientitymanaged = 0; + $this->isextrafieldmanaged = 1; + + if (!getDolGlobalInt('MAIN_SHOW_TECHNICAL_ID') && isset($this->fields['rowid']) && !empty($this->fields['ref'])) { + $this->fields['rowid']['visible'] = 0; + } + if (!isModEnabled('multicompany') && isset($this->fields['entity'])) { + $this->fields['entity']['enabled'] = 0; + } + + // Example to show how to set values of fields definition dynamically + /*if ($user->hasRight('test', 'mailtemplate', 'read')) { + $this->fields['myfield']['visible'] = 1; + $this->fields['myfield']['noteditable'] = 0; + }*/ + + // Unset fields that are disabled + foreach ($this->fields as $key => $val) { + if (isset($val['enabled']) && empty($val['enabled'])) { + unset($this->fields[$key]); + } + } + + // Translate some data of arrayofkeyval + if (is_object($langs)) { + foreach ($this->fields as $key => $val) { + if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval'])) { + foreach ($val['arrayofkeyval'] as $key2 => $val2) { + $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2); + } + } + } + } + } + + + /** + * Load object in memory from the database + * + * @param int $id Id object + * @param string $ref Ref + * @param int $noextrafields 0=Default to load extrafields, 1=No extrafields + * @param int $nolines 0=Default to load extrafields, 1=No extrafields + * @return int Return integer <0 if KO, 0 if not found, >0 if OK + */ + public function fetch($id, $ref = null, $noextrafields = 0, $nolines = 0) + { + $result = $this->fetchCommon($id, $ref, '', $noextrafields); + if ($result > 0 && !empty($this->table_element_line) && empty($nolines)) { + $this->fetchLines($noextrafields); + } + return $result; + } } diff --git a/htdocs/modulebuilder/template/class/myobject.class.php b/htdocs/modulebuilder/template/class/myobject.class.php index ef79e85f748..a44f3c08cfe 100644 --- a/htdocs/modulebuilder/template/class/myobject.class.php +++ b/htdocs/modulebuilder/template/class/myobject.class.php @@ -35,17 +35,17 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; class MyObject extends CommonObject { /** - * @var string ID of module. + * @var string ID of module. */ public $module = 'mymodule'; /** - * @var string ID to identify managed object. + * @var string ID to identify managed object. */ public $element = 'myobject'; /** - * @var string Name of table without prefix where object is stored. This is also the key used for extrafields management (so extrafields know the link to the parent table). + * @var string Name of table without prefix where object is stored. This is also the key used for extrafields management (so extrafields know the link to the parent table). */ public $table_element = 'mymodule_myobject'; @@ -55,7 +55,7 @@ class MyObject extends CommonObject //public $element_for_permission = 'mymodule'; /** - * @var string String with name of icon for myobject. Must be a 'fa-xxx' fontawesome code (or 'fa-xxx_fa_color_size') or 'myobject@mymodule' if picto is file 'img/object_myobject.png'. + * @var string String with name of icon for myobject. Must be a 'fa-xxx' fontawesome code (or 'fa-xxx_fa_color_size') or 'myobject@mymodule' if picto is file 'img/object_myobject.png'. */ public $picto = 'fa-file'; From 6401dcb414360fff0121742540bdce6c60185ba0 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 23 Jul 2024 09:06:24 +0200 Subject: [PATCH 05/20] Fix look and feel v20 --- htdocs/comm/propal/card.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/comm/propal/card.php b/htdocs/comm/propal/card.php index 7d93582d64f..69182578865 100644 --- a/htdocs/comm/propal/card.php +++ b/htdocs/comm/propal/card.php @@ -2517,7 +2517,7 @@ if ($action == 'create') { $morehtmlref .= $form->editfieldkey("RefCustomer", 'ref_client', $object->ref_client, $object, $usercancreate, 'string', '', 0, 1); $morehtmlref .= $form->editfieldval("RefCustomer", 'ref_client', $object->ref_client, $object, $usercancreate, 'string'.(isset($conf->global->THIRDPARTY_REF_INPUT_SIZE) ? ':' . getDolGlobalString('THIRDPARTY_REF_INPUT_SIZE') : ''), '', null, null, '', 1); // Thirdparty - $morehtmlref .= '
'.$langs->trans('ThirdParty').' : '.$soc->getNomUrl(1, 'customer'); + $morehtmlref .= '
'.$soc->getNomUrl(1, 'customer'); if (!getDolGlobalString('MAIN_DISABLE_OTHER_LINK') && $soc->id > 0) { $morehtmlref .= ' ('.$langs->trans("OtherProposals").')'; } From 12f85b750167910e482ef2761109d18e37a77c90 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 23 Jul 2024 09:23:12 +0200 Subject: [PATCH 06/20] Fix missing param in constructor --- htdocs/core/class/html.formmail.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index 5c4715e9afb..01c1e989ce4 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -1594,7 +1594,7 @@ class FormMail extends Form return -1; } - $ret = new ModelMail(); + $ret = new ModelMail($dbs); $languagetosearch = (is_object($outputlangs) ? $outputlangs->defaultlang : ''); // Define $languagetosearchmain to fall back on main language (for example to get 'es_ES' for 'es_MX') @@ -1792,7 +1792,7 @@ class FormMail extends Form } } - $line = new ModelMail(); + $line = new ModelMail($dbs); $line->id = $obj->rowid; $line->label = $obj->label; $line->lang = $obj->lang; From deee0ab320a09d7022cf225d0c7f86a6ccef2a8d Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 23 Jul 2024 09:25:23 +0200 Subject: [PATCH 07/20] Fix bad param of constructor --- htdocs/core/class/html.formmail.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index 01c1e989ce4..cb191810031 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -1765,7 +1765,7 @@ class FormMail extends Form */ public function fetchAllEMailTemplate($type_template, $user, $outputlangs, $active = 1) { - global $conf; + global $db, $conf; $sql = "SELECT rowid, module, label, topic, content, content_lines, lang, fk_user, private, position"; $sql .= " FROM ".$this->db->prefix().'c_email_templates'; @@ -1792,7 +1792,7 @@ class FormMail extends Form } } - $line = new ModelMail($dbs); + $line = new ModelMail($db); $line->id = $obj->rowid; $line->label = $obj->label; $line->lang = $obj->lang; From ff5c45b39e2dfbfcb32329839683c7519d786edd Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 23 Jul 2024 09:39:53 +0200 Subject: [PATCH 08/20] Fix css to allow to avoid behaviour change of v20 --- htdocs/projet/card.php | 8 +++++--- htdocs/theme/eldy/global.inc.php | 6 +++--- htdocs/theme/md/style.css.php | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/htdocs/projet/card.php b/htdocs/projet/card.php index 604002627a5..588bc575d67 100644 --- a/htdocs/projet/card.php +++ b/htdocs/projet/card.php @@ -4,8 +4,8 @@ * Copyright (C) 2005-2012 Regis Houssin * Copyright (C) 2023 Charlene Benke * Copyright (C) 2023 Christian Foellmann - * Copyright (C) 2024 MDW - * Copyright (C) 2024 Frédéric France + * 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 @@ -1443,7 +1443,9 @@ if ($action == 'create' && $user->hasRight('projet', 'creer')) { // Description print '
'; // Categories diff --git a/htdocs/theme/eldy/global.inc.php b/htdocs/theme/eldy/global.inc.php index bdb8c03d9d1..aa60b9aa452 100644 --- a/htdocs/theme/eldy/global.inc.php +++ b/htdocs/theme/eldy/global.inc.php @@ -1071,14 +1071,14 @@ td.wordbreak img, td.wordbreakimp img { .text-warning{ color : } -/* CSS used for extrafield text */ +/* CSS used for long description and extrafield text */ .shortmessagecut { - max-height: 125px; + max-height: px; max-width: 100%; overflow-y: auto; } .longmessagecut { - max-height: 250px; + max-height: px; max-width: 100%; overflow-y: auto; } diff --git a/htdocs/theme/md/style.css.php b/htdocs/theme/md/style.css.php index 8f24dad1b51..ad09b4177da 100644 --- a/htdocs/theme/md/style.css.php +++ b/htdocs/theme/md/style.css.php @@ -1258,12 +1258,12 @@ td.wordbreak img, td.wordbreakimp img { } /* CSS used for extrafield text */ .shortmessagecut { - max-height: 125px; + max-height: px; max-width: 100%; overflow-y: auto; } .longmessagecut { - max-height: 250px; + max-height: px; max-width: 100%; overflow-y: auto; } From 9c33ddc4a3001f9f70a76ba4665014f3a8d743d7 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 23 Jul 2024 10:00:54 +0200 Subject: [PATCH 09/20] Clean tooltip content --- htdocs/comm/action/class/actioncomm.class.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/htdocs/comm/action/class/actioncomm.class.php b/htdocs/comm/action/class/actioncomm.class.php index 9989d6874bb..9a439752aea 100644 --- a/htdocs/comm/action/class/actioncomm.class.php +++ b/htdocs/comm/action/class/actioncomm.class.php @@ -1586,7 +1586,7 @@ class ActionComm extends CommonObject */ public function getTooltipContentArray($params) { - global $conf, $langs, $user; + global $langs; $langs->load('agenda'); $datas = array(); @@ -1635,7 +1635,7 @@ class ActionComm extends CommonObject } */ } if (!empty($this->note_private)) { - $datas['description'] = '
'.$langs->trans('Description').':
'; + $datas['description'] = '

'; // Try to limit length of content $texttoshow = dolGetFirstLineOfText($this->note_private, 10); // Restrict height of content into the tooltip @@ -1736,7 +1736,7 @@ class ActionComm extends CommonObject } */ } if (!empty($this->note_private)) { - $tooltip .= '

'.$langs->trans('Description').':
'; + $tooltip .= '

'; $texttoshow = dolGetFirstLineOfText($this->note_private, 8); // Try to limit length of content $tooltip .= '
'; // Restrict height of content into the tooltip $tooltip .= (dol_textishtml($texttoshow) ? str_replace(array("\r", "\n"), "", $texttoshow) : str_replace(array("\r", "\n"), '
', $texttoshow)); @@ -1849,8 +1849,6 @@ class ActionComm extends CommonObject */ public function getTypePicto($morecss = 'pictofixedwidth paddingright valignmiddle', $titlealt = '') { - global $conf; - $imgpicto = ''; if (getDolGlobalString('AGENDA_USE_EVENT_TYPE')) { $color = ''; From 7160edef07143f4a138c600afc6eca533479de0d Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 23 Jul 2024 10:24:39 +0200 Subject: [PATCH 10/20] Fix string not correctly sanitized - Debug v20 --- htdocs/core/lib/functions.lib.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 097a0e9a18a..90cc92d576a 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -2016,9 +2016,10 @@ function dol_escape_htmltag($stringtoescape, $keepb = 0, $keepn = 0, $noescapeta $result = str_ireplace('__SRCHTTPIMG', 'src="http:', $result); $result = str_ireplace('__SRCHTTPSIMG', 'src="https:', $result); $result = str_ireplace('__DOUBLEQUOTE', '"', $result); - $result = str_ireplace('__SIMPLEQUOTE', ''', $result); } + $result = str_ireplace('__SIMPLEQUOTE', ''', $result); + //$result="\n\n\n".var_export($tmp, true)."\n\n\n".var_export($result, true); return $result; From 745b34c176d328d08a06c4734f3b23a0c608d58a Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 23 Jul 2024 10:36:51 +0200 Subject: [PATCH 11/20] Fix css --- htdocs/comm/action/card.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/htdocs/comm/action/card.php b/htdocs/comm/action/card.php index 20be4780af2..5bec17b11b7 100644 --- a/htdocs/comm/action/card.php +++ b/htdocs/comm/action/card.php @@ -1279,14 +1279,13 @@ if ($action == 'create') { // Full day print '
'; - print ''; - $actionstatic = $actioncomm; - // Example: Email sent from invoice card //$actionstatic->code = 'AC_BILL_SENTBYMAIL //$actionstatic->type_code = 'AC_OTHER_AUTO' // Type - $labeltype = $actionstatic->type_code; - if (!getDolGlobalString('AGENDA_USE_EVENT_TYPE') && empty($arraylist[$labeltype])) { - $labeltype = 'AC_OTH'; - } - if (preg_match('/^TICKET_MSG/', $actionstatic->code)) { - $labeltype = $langs->trans("Message"); - } else { - if (!empty($arraylist[$labeltype])) { - $labeltype = $arraylist[$labeltype]; - } - if ($actionstatic->type_code == 'AC_OTH_AUTO' && ($actionstatic->type_code != $actionstatic->code) && $labeltype && !empty($arraylist[$actionstatic->code])) { - $labeltype .= ' - '.$arraylist[$actionstatic->code]; // Use code in priority on type_code - } - } + $labeltype = $actioncomm->getTypeLabel(0); print ''; diff --git a/htdocs/theme/eldy/global.inc.php b/htdocs/theme/eldy/global.inc.php index acee6586460..8716cb3fcd0 100644 --- a/htdocs/theme/eldy/global.inc.php +++ b/htdocs/theme/eldy/global.inc.php @@ -613,6 +613,10 @@ input.pageplusone { .colorblack { color: var(--colorblack); } +.colorblack.totalnboflines { + font-size: 90%; + opacity: 0.5; +} .fontsizeunset { font-size: unset !important; } @@ -5949,6 +5953,10 @@ div.jPicker table.jPicker { padding-right: 20px; padding-left: 20px; } +table.jPicker tr:first-of-type td { + height: 2px !important; + line-height: 2px; +} .jPicker .Move { background: unset !important; border: unset !important; @@ -5976,6 +5984,13 @@ table.jPicker { .jPicker td.Text { white-space: nowrap; } +.jPicker td.Text input { + height: 1em !important; +} +.jPicker .Preview div { + height: 36px !important; +} + A.color, A.color:active, A.color:visited { position : relative; display : block; diff --git a/htdocs/theme/md/style.css.php b/htdocs/theme/md/style.css.php index 499837ea7bf..582de5cfb7b 100644 --- a/htdocs/theme/md/style.css.php +++ b/htdocs/theme/md/style.css.php @@ -807,6 +807,10 @@ input.pageplusone, .divadvancedsearchfieldcompinput { .colorblack { color: var(--colorblack); } +.colorblack.totalnboflines { + font-size: 95%; + opacity: 0.5; +} .fontsizeunset { font-size: unset !important; } @@ -5940,6 +5944,10 @@ div.jPicker table.jPicker { padding-right: 20px; padding-left: 20px; } +table.jPicker tr:first-of-type td { + height: 2px !important; + line-height: 2px; +} .jPicker .Move { background: unset !important; border: unset !important; @@ -5967,6 +5975,12 @@ table.jPicker { .jPicker td.Text { white-space: nowrap; } +.jPicker td.Text input { + height: 1em !important; +} +.jPicker .Preview div { + height: 36px !important; +} A.color, A.color:active, A.color:visited { position : relative;
'.$langs->trans("Description").''; - print dol_htmlentitiesbr($object->description); + print '
'; + print dolPrintHTML($object->description); + print '
'; print '
'.$langs->trans("Date").''; + print ''; // Recurring event $userepeatevent = (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 1 ? 1 : 0); if ($userepeatevent) { // Repeat - //print '
'; - print '        
'; + print '        
'; print img_picto($langs->trans("Recurrence"), 'recurring', 'style="margin-left: 6px" class="paddingright2"'); print ''; From 0a076813322d3ea57892e55f0fd87f8b0c59ff9d Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 23 Jul 2024 11:49:06 +0200 Subject: [PATCH 12/20] Fix label of type of event not consistent between screen --- htdocs/comm/action/class/actioncomm.class.php | 75 +++++++++++++++---- htdocs/core/class/html.formactions.class.php | 23 +----- htdocs/langs/en_US/main.lang | 1 + 3 files changed, 64 insertions(+), 35 deletions(-) diff --git a/htdocs/comm/action/class/actioncomm.class.php b/htdocs/comm/action/class/actioncomm.class.php index 9a439752aea..05346c589b9 100644 --- a/htdocs/comm/action/class/actioncomm.class.php +++ b/htdocs/comm/action/class/actioncomm.class.php @@ -1693,31 +1693,35 @@ class ActionComm extends CommonObject $result = ''; // Set label of type - $labeltype = ''; - if ($this->type_code) { - $langs->load("commercial"); - $labeltype = ($langs->transnoentities("Action".$this->type_code) != "Action".$this->type_code) ? $langs->transnoentities("Action".$this->type_code) : $this->type_label; - } - if (!getDolGlobalString('AGENDA_USE_EVENT_TYPE')) { - if ($this->type_code != 'AC_OTH_AUTO') { - $labeltype = $langs->trans('ActionAC_MANUAL'); - } - } + $labeltype = $this->getTypeLabel(1); $tooltip = img_picto('', $this->picto).' '.$langs->trans('Action').''; + + $tooltip .= '   -   '.$this->getTypePicto('pictofixedwidth paddingright valignmiddle').$labeltype; if (!empty($this->ref)) { $tooltip .= '
'.$langs->trans('Ref').': '.dol_escape_htmltag($this->ref); } if (!empty($label)) { $tooltip .= '
'.$langs->trans('Title').': '.dol_escape_htmltag($label); } - if (!empty($labeltype)) { - $tooltip .= '
'.$langs->trans('Type').': '.dol_escape_htmltag($labeltype); - } if (!empty($this->location)) { $tooltip .= '
'.$langs->trans('Location').': '.dol_escape_htmltag($this->location); } - if (isset($this->transparency)) { + + $tooltip .= '
'.$langs->trans('Date').': '.dol_print_date($this->datep, 'dayhourreduceformat', 'tzuserrel'); + if ($this->datef) { + $tmpa = dol_getdate($this->datep); + $tmpb = dol_getdate($this->datef); + if ($tmpa['mday'] == $tmpb['mday'] && $tmpa['mon'] == $tmpb['mon'] && $tmpa['year'] == $tmpb['year']) { + if ($tmpa['hours'] != $tmpb['hours'] || $tmpa['minutes'] != $tmpb['minutes']) { + $tooltip .= '-'.dol_print_date($this->datef, 'hour', 'tzuserrel'); + } + } else { + $tooltip .= '-'.dol_print_date($this->datef, 'dayhourreduceformat', 'tzuserrel'); + } + } + + if ($this->datef && $this->datep != $this->datef && isset($this->transparency)) { $tooltip .= '
'.$langs->trans('Busy').': '.yn($this->transparency); } if (!empty($this->email_msgid)) { @@ -1889,6 +1893,49 @@ class ActionComm extends CommonObject } + /** + * Return label of type of event + * + * @param string $mode 0=Mode short, 1=Mode long + * @return string HTML String + */ + public function getTypeLabel($mode = 0) + { + global $conf, $langs; + + // If cache for array of types unknown, we load it + if (!empty($conf->cache['actioncommgetypelabel'])) { + $arraylist = $conf->cache['actioncommgetypelabel']; + } else { + require_once DOL_DOCUMENT_ROOT.'/comm/action/class/cactioncomm.class.php'; + $caction = new CActionComm($this->db); + $arraylist = $caction->liste_array(1, 'code', '', (getDolGlobalString('AGENDA_USE_EVENT_TYPE') ? 0 : 1), '', 1); + $conf->cache['actioncommgetypelabel'] = $arraylist; + } + + $labeltype = $this->type_code; + if (!getDolGlobalString('AGENDA_USE_EVENT_TYPE') && empty($arraylist[$labeltype])) { + $labeltype = 'AC_OTH'; + } + if (preg_match('/^TICKET_MSG/', $this->code)) { + $labeltype = $langs->trans("Message"); + } else { + if (!empty($arraylist[$labeltype])) { + $labeltype = $arraylist[$labeltype]; + } + if ($this->type_code == 'AC_OTH_AUTO' && ($this->type_code != $this->code) && $labeltype && !empty($arraylist[$this->code])) { + $labeltype .= ' - '.$arraylist[$this->code]; // Use code in priority over type_code + } + } + + if ($this->type == 'systemauto' && $mode == 1) { + $labeltype .= ' ('.$langs->trans("auto").')'; + } + + + return $labeltype; + } + /** * Sets object to supplied categories. * diff --git a/htdocs/core/class/html.formactions.class.php b/htdocs/core/class/html.formactions.class.php index 30c5ac70fd9..337f211f182 100644 --- a/htdocs/core/class/html.formactions.class.php +++ b/htdocs/core/class/html.formactions.class.php @@ -172,7 +172,7 @@ class FormActions */ public function showactions($object, $typeelement, $socid = 0, $forceshowtitle = 0, $morecss = 'listactions', $max = 0, $moreparambacktopage = '', $morehtmlcenter = '', $assignedtouser = 0) { - global $langs, $conf, $user, $hookmanager; + global $langs, $user, $hookmanager; require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php'; @@ -185,10 +185,6 @@ class FormActions dol_print_error($this->db, 'FailedToGetActions'); } - require_once DOL_DOCUMENT_ROOT.'/comm/action/class/cactioncomm.class.php'; - $caction = new CActionComm($this->db); - $arraylist = $caction->liste_array(1, 'code', '', (!getDolGlobalString('AGENDA_USE_EVENT_TYPE') ? 1 : 0), '', 1); - $num = count($listofactions); if ($num || $forceshowtitle) { $title = $langs->trans("LatestLinkedEvents", $max ? $max : ''); @@ -308,27 +304,12 @@ class FormActions } print '
'; print $actioncomm->getTypePicto(); print $labeltype; diff --git a/htdocs/langs/en_US/main.lang b/htdocs/langs/en_US/main.lang index 18dc30a2c5f..81a316b7f68 100644 --- a/htdocs/langs/en_US/main.lang +++ b/htdocs/langs/en_US/main.lang @@ -1299,3 +1299,4 @@ AllEntities=All entities TranslationOfKey=Translation of the key AnyTranslationKey SignedStatus=Signed status NbRecordQualified=Number of qualified records +auto=auto From 5a7319664e739a3ee1c12d29f9422396500f3a6d Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 23 Jul 2024 13:12:18 +0200 Subject: [PATCH 13/20] Fix css --- htdocs/theme/eldy/global.inc.php | 2 +- htdocs/theme/md/style.css.php | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/htdocs/theme/eldy/global.inc.php b/htdocs/theme/eldy/global.inc.php index aa60b9aa452..2dbe39b6416 100644 --- a/htdocs/theme/eldy/global.inc.php +++ b/htdocs/theme/eldy/global.inc.php @@ -3470,7 +3470,7 @@ a.vmenu:link, a.vmenu:visited { color: var(--colortextbackvmenu); } -a.vsmenu:link, a.vsmenu:visited, a.vsmenu:hover, a.vsmenu:active, span.vsmenu { +a.vsmenu:link, a.vsmenu:visited, a.vsmenu:hover, a.vsmenu:active, span.vmenu, span.vsmenu { font-family: ; text-align: ; color: var(--colortextbackvmenu); diff --git a/htdocs/theme/md/style.css.php b/htdocs/theme/md/style.css.php index ad09b4177da..3c1be5cc638 100644 --- a/htdocs/theme/md/style.css.php +++ b/htdocs/theme/md/style.css.php @@ -3584,7 +3584,11 @@ a.vmenu:link, a.vmenu:visited { color: var(--colortextbackvmenu); } -a.vsmenu:link, a.vsmenu:visited, a.vsmenu:hover, a.vsmenu:active, span.vsmenu { font-size:; font-family: ; text-align: ; font-weight: normal; color: #202020; margin: 1px 1px 1px 8px; } +a.vsmenu:link, a.vsmenu:visited, a.vsmenu:hover, a.vsmenu:active, span.vmenu, span.vsmenu { + font-size:; font-family: ; text-align: ; font-weight: normal; + color: var(--colortextbackvmenu); + margin: 1px 1px 1px 8px; +} span.vsmenudisabled:not(.spanlilevel0), font.vsmenudisabled:not(.spanlilevel0) { font-size:; } From 56ed17a2c4d3d58d3f40826d9fa53be592cef12a Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 23 Jul 2024 16:19:08 +0200 Subject: [PATCH 14/20] Disallow $_ into php code. --- 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 90cc92d576a..abaaf55de18 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -10312,7 +10312,7 @@ function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1' } // We block use of php exec or php file functions - $forbiddenphpstrings = array('$$'); + $forbiddenphpstrings = array('$$', '$_'); $forbiddenphpstrings = array_merge($forbiddenphpstrings, array('_ENV', '_SESSION', '_COOKIE', '_GET', '_POST', '_REQUEST', 'ReflectionFunction')); $forbiddenphpfunctions = array("exec", "passthru", "shell_exec", "system", "proc_open", "popen"); From 1d04c0a37dc30a0de4b4d7f768ef5930b055a6d4 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 23 Jul 2024 16:37:58 +0200 Subject: [PATCH 15/20] Fix #yogosha23433 - Disallow }[ and call_user_func into php code of website. --- htdocs/core/lib/functions.lib.php | 11 ++++++---- htdocs/core/lib/website2.lib.php | 36 ++++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index abaaf55de18..1ff10cdec9b 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -10217,7 +10217,7 @@ function verifCond($strToEvaluate, $onlysimplestring = '1') * '1' (most common use)=Accept only simple string with char 'a-z0-9\s^$_+-.*>&|=!?():"\',/@';', * '2' (used for example for the compute property of extrafields)=Accept also '[]' * @return void|string Nothing or 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() + * @see verifCond(), checkPHPCode() to see sanitizing rules that should be very close. * @phan-suppress PhanPluginUnsafeEval */ function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1') @@ -10312,16 +10312,18 @@ function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1' } // We block use of php exec or php file functions - $forbiddenphpstrings = array('$$', '$_'); + $forbiddenphpstrings = array('$$', '$_', '}['); $forbiddenphpstrings = array_merge($forbiddenphpstrings, array('_ENV', '_SESSION', '_COOKIE', '_GET', '_POST', '_REQUEST', 'ReflectionFunction')); - $forbiddenphpfunctions = array("exec", "passthru", "shell_exec", "system", "proc_open", "popen"); - $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "executeCLI", "verifCond")); // native dolibarr functions + $forbiddenphpfunctions = array(); $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("base64_decode", "rawurldecode", "urldecode", "str_rot13", "hex2bin")); // decode string functions used to obfuscated function name $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("fopen", "file_put_contents", "fputs", "fputscsv", "fwrite", "fpassthru", "require", "include", "mkdir", "rmdir", "symlink", "touch", "unlink", "umask")); + $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")); $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", "executeCLI", "verifCond")); // native dolibarr functions $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_compress_dir", "dol_decode", "dol_delete_file", "dol_delete_dir", "dol_delete_dir_recursive", "dol_copy", "archiveOrBackupFile")); // more dolibarr functions @@ -10339,6 +10341,7 @@ function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1' //$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) { diff --git a/htdocs/core/lib/website2.lib.php b/htdocs/core/lib/website2.lib.php index 1ac81b4a5bf..9f6ef83a829 100644 --- a/htdocs/core/lib/website2.lib.php +++ b/htdocs/core/lib/website2.lib.php @@ -679,7 +679,7 @@ function showWebsiteTemplates(Website $website) * @param string $phpfullcodestringold PHP old string (before the change). For example "" * @param string $phpfullcodestring PHP new string. For example "" * @return int Error or not - * @see dolKeepOnlyPhpCode() + * @see dolKeepOnlyPhpCode(), dol_eval() to see sanitizing rules that should be very close. */ function checkPHPCode(&$phpfullcodestringold, &$phpfullcodestring) { @@ -701,20 +701,46 @@ function checkPHPCode(&$phpfullcodestringold, &$phpfullcodestring) // Then check forbidden commands if (!$error) { - $forbiddenphpcommands = array("override_function", "session_id", "session_create_id", "session_regenerate_id"); + $forbiddenphpstrings = array('$$', '}['); + $forbiddenphpstrings = array_merge($forbiddenphpstrings, array('ReflectionFunction')); + + $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("call_user_func")); + //$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once")); if (!getDolGlobalString('WEBSITE_PHP_ALLOW_EXEC')) { // If option is not on, we disallow functions to execute commands - $forbiddenphpcommands = array_merge($forbiddenphpcommands, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen", "eval", "dol_eval", "executeCLI")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "executeCLI", "verifCond")); // native dolibarr functions + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities } if (!getDolGlobalString('WEBSITE_PHP_ALLOW_WRITE')) { // If option is not on, we disallow functions to write files - $forbiddenphpcommands = array_merge($forbiddenphpcommands, array("fopen", "file_put_contents", "fputs", "fputscsv", "fwrite", "fpassthru", "unlink", "mkdir", "rmdir", "symlink", "touch", "umask")); + $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("fopen", "file_put_contents", "fputs", "fputscsv", "fwrite", "fpassthru", "mkdir", "rmdir", "symlink", "touch", "unlink", "umask")); } - foreach ($forbiddenphpcommands as $forbiddenphpcommand) { + + $forbiddenphpmethods = array('invoke', 'invokeArgs'); // Method of ReflectionFunction to execute a function + + foreach ($forbiddenphpstrings as $forbiddenphpstring) { + if (preg_match('/'.preg_quote($forbiddenphpstring, '/').'/ms', $phpfullcodestring)) { + $error++; + setEventMessages($langs->trans("DynamicPHPCodeContainsAForbiddenInstruction", $forbiddenphpstring), null, 'errors'); + break; + } + } + foreach ($forbiddenphpfunctions as $forbiddenphpcommand) { if (preg_match('/'.$forbiddenphpcommand.'\s*\(/ms', $phpfullcodestring)) { $error++; setEventMessages($langs->trans("DynamicPHPCodeContainsAForbiddenInstruction", $forbiddenphpcommand), null, 'errors'); break; } } + foreach ($forbiddenphpmethods as $forbiddenphpmethod) { + if (preg_match('/->'.$forbiddenphpmethod.'/ms', $phpfullcodestring)) { + $error++; + setEventMessages($langs->trans("DynamicPHPCodeContainsAForbiddenInstruction", $forbiddenphpmethod), null, 'errors'); + break; + } + } } // This char can be used to execute RCE for example using with echo `ls` From 7595609be22fd50d7877c1b0b90357d64e605078 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 23 Jul 2024 18:27:18 +0200 Subject: [PATCH 16/20] Fix #yogosha23464 possible RCE by an admin user. --- htdocs/core/lib/functions.lib.php | 34 ++++++++++++++++++++++++------- test/phpunit/SecurityTest.php | 10 ++++++--- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 1ff10cdec9b..4bb177a82c3 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -10244,6 +10244,8 @@ function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1' // 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) > 0) && ($secondloadedobj = new Project($db)) && ($secondloadedobj->fetchNoCompute($reloadedobj->fk_project) > 0)) ? $secondloadedobj->ref : "Parent project not found" + + // Check if there is dynamic call (first we check chars are all into use a whitelist chars) $specialcharsallowed = '^$_+-.*>&|=!?():"\',/@'; if ($onlysimplestring == '2') { $specialcharsallowed .= '[]'; @@ -10255,17 +10257,29 @@ function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1' if ($returnvalue) { return 'Bad string syntax to evaluate (found chars that are not chars for a simple clean eval string): '.$s; } else { - dol_syslog('Bad string syntax to evaluate (found chars that are not chars for a simple clean eval string): '.$s); + dol_syslog('Bad string syntax to evaluate (found chars that are not chars for a simple clean eval string): '.$s, LOG_WARNING); return ''; } } + + // 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 of "$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 of "$abc(" or "$abc (" instead of using the direct name of the function): '.$s, LOG_WARNING); + return ''; + } + } + + // Now we check if we try dynamic call (by removing white list pattern of using parenthesis then testing if a parenthesis exists) $savescheck = ''; $scheck = $s; while ($scheck && $savescheck != $scheck) { $savescheck = $scheck; $scheck = preg_replace('/->[a-zA-Z0-9_]+\(/', '->__METHOD__', $scheck); // accept parenthesis in '...->method(...' $scheck = preg_replace('/^\(/', '__PARENTHESIS__ ', $scheck); // accept parenthesis in '(...'. Must replace with __PARENTHESIS__ with a space after to allow following substitutions - $scheck = preg_replace('/\s\(/', '__PARENTHESIS__ ', $scheck); // accept parenthesis in '... ('. Must replace with __PARENTHESIS__ with a space after to allow following substitutions + $scheck = preg_replace('/\s\(/', '__PARENTHESIS__ ', $scheck); // accept parenthesis in '... (' like in 'if ($a == 1)'. Must replace with __PARENTHESIS__ with a space after to allow following substitutions $scheck = preg_replace('/^!?[a-zA-Z0-9_]+\(/', '__FUNCTION__', $scheck); // accept parenthesis in 'function(' and '!function(' $scheck = preg_replace('/\s!?[a-zA-Z0-9_]+\(/', '__FUNCTION__', $scheck); // accept parenthesis in '... function(' and '... !function(' $scheck = preg_replace('/(\^|\')\(/', '__REGEXSTART__', $scheck); // To allow preg_match('/^(aaa|bbb)/'... or isStringVarMatching('leftmenu', '(aaa|bbb)') @@ -10275,22 +10289,28 @@ function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1' 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); + 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: // $db, $langs, $leftmenu, $topmenu, $user, $langs, $objectoffield, $object..., } if (is_array($s) || $s === 'Array') { - return 'Bad string syntax to evaluate (value is Array) '.var_export($s, true); + 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 (strpos($s, '::') !== false) { if ($returnvalue) { return 'Bad string syntax to evaluate (double : char is forbidden): '.$s; } else { - dol_syslog('Bad string syntax to evaluate (double : char is forbidden): '.$s); + dol_syslog('Bad string syntax to evaluate (double : char is forbidden): '.$s, LOG_WARNING); return ''; } } @@ -10298,7 +10318,7 @@ function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1' 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); + dol_syslog('Bad string syntax to evaluate (backtick char is forbidden): '.$s, LOG_WARNING); return ''; } } @@ -10306,7 +10326,7 @@ function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1' 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); + dol_syslog('Bad string syntax to evaluate (dot char is forbidden): '.$s, LOG_WARNING); return ''; } } diff --git a/test/phpunit/SecurityTest.php b/test/phpunit/SecurityTest.php index 017f69803af..89864c36451 100644 --- a/test/phpunit/SecurityTest.php +++ b/test/phpunit/SecurityTest.php @@ -1107,16 +1107,20 @@ class SecurityTest extends CommonClassTest $a = 'ab'; $result = (string) dol_eval("(\$a.'s')", 1, 0); print "result19 = ".$result."\n"; - $this->assertStringContainsString('Bad string syntax to evaluate', $result); + $this->assertStringContainsString('Bad string syntax to evaluate', $result, 'Test 19'); $leftmenu = 'abs'; $result = (string) dol_eval('$leftmenu(-5)', 1, 0); print "result20 = ".$result."\n"; - $this->assertStringContainsString('Bad string syntax to evaluate', $result); + $this->assertStringContainsString('Bad string syntax to evaluate', $result, 'Test 20'); $result = (string) dol_eval('str_replace("z","e","zxzc")("whoami");', 1, 0); print "result21 = ".$result."\n"; - $this->assertStringContainsString('Bad string syntax to evaluate', $result); + $this->assertStringContainsString('Bad string syntax to evaluate', $result, 'Test 21'); + + $result = (string) dol_eval('($a = "ex") && ($b = "ec") && ($cmd = "$a$b") && $cmd ("curl localhost:5555")', 1, 0); + print "result22 = ".$result."\n"; + $this->assertStringContainsString('Bad string syntax to evaluate', $result, 'Test 22'); } /** From 2bdcbabeae68b844204dfc3560e337ccf659e587 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 24 Jul 2024 02:19:49 +0200 Subject: [PATCH 17/20] Avoid memory error on browser side --- htdocs/core/lib/functions.lib.php | 7 ++++--- htdocs/theme/eldy/global.inc.php | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 4bb177a82c3..1705717d30b 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -6405,9 +6405,10 @@ function print_fleche_navigation($page, $file, $options = '', $nextpage = 0, $be if (empty($hidenavigation)) { if ((int) $limit > 0 && empty($hideselectlimit)) { $pagesizechoices = '10:10,15:15,20:20,25:25,50:50,100:100,250:250,500:500,1000:1000'; - $pagesizechoices .= ',5000:5000,10000:10000,20000:20000'; - //$pagesizechoices.=',0:'.$langs->trans("All"); // Not yet supported - //$pagesizechoices.=',2:2'; + $pagesizechoices .= ',5000:5000,10000:10000'; + //$pagesizechoices .= ',20000:20000'; // Memory trouble on browsers + //$pagesizechoices .= ',0:'.$langs->trans("All"); // Not yet supported + //$pagesizechoices .= ',2:2'; if (getDolGlobalString('MAIN_PAGESIZE_CHOICES')) { $pagesizechoices = getDolGlobalString('MAIN_PAGESIZE_CHOICES'); } diff --git a/htdocs/theme/eldy/global.inc.php b/htdocs/theme/eldy/global.inc.php index 2dbe39b6416..7b06e5b7335 100644 --- a/htdocs/theme/eldy/global.inc.php +++ b/htdocs/theme/eldy/global.inc.php @@ -3707,6 +3707,9 @@ div.tabBar { border-top: 1px solid #BBB; /* border-bottom: 1px solid #AAA; */ width: auto; + background: var(--colorbackbody); +} +table.noborder { background: var(--colorbacktabcard1); } div.tabBar tr.titre td { @@ -4978,6 +4981,9 @@ a.valignmiddle.dashboardlineindicator { .height30 { height: 30px !important; } +.height50 { + height: 50px !important; +} tr.box_titre { height: 26px; From 25476547213407e93583cb107f2268d0f7ced384 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 24 Jul 2024 01:57:35 +0200 Subject: [PATCH 18/20] Debug v20 - CSS --- htdocs/core/lib/functions.lib.php | 4 ++-- htdocs/theme/eldy/btn.inc.php | 18 +++++++++++------- htdocs/theme/eldy/global.inc.php | 13 +++++++++---- htdocs/theme/md/btn.inc.php | 5 +++-- htdocs/theme/md/style.css.php | 24 ++++++++++++++++++------ 5 files changed, 43 insertions(+), 21 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 1705717d30b..54c8ae3ad3e 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -6322,7 +6322,7 @@ function print_barre_liste($title, $page, $file, $options = '', $sortfield = '', do { if ($pagenavastextinput) { if ($cpt == $page) { - $pagelist .= ''; + $pagelist .= ''; $pagelist .= '/'; } } else { @@ -6418,7 +6418,7 @@ function print_fleche_navigation($page, $file, $options = '', $nextpage = 0, $be print ''; print ''; } else { - print '
  • '; + print '
  • '; print '