diff --git a/ChangeLog b/ChangeLog index b72720f75a6..bc49acb9ef2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -164,6 +164,8 @@ NEW: vat rate with department in dict (#31628) (#31627) NEW: When we export data of unlaterable log, we add an unalterable line in logs NEW Add option THEME_STICKY_TOPMENU = 'scrollleftmenu_after_mainpage' (or 'disabled') NEW value for FICHINTER_DISABLE_DETAILS. If FICHINTER_DISABLE_DETAILS is set to '2' details are disabled only on intervention list. +SEC: security avoid RCE using -'- sequence to pass --checkpoint-action parameter in tar command. +SEC: FIX Security path transversal with modulepart=medias (viewimage.php and download.php) PERF: Reduce nb of requests into num_public_holiday PERF: Reduce size for VCF files and virtualcard qrcode diff --git a/htdocs/bookmarks/bookmarks.lib.php b/htdocs/bookmarks/bookmarks.lib.php index fbf2bd232cf..16b25558987 100644 --- a/htdocs/bookmarks/bookmarks.lib.php +++ b/htdocs/bookmarks/bookmarks.lib.php @@ -91,8 +91,8 @@ function printDropdownBookmarksList() $newbtn = ''; if ($user->hasRight('bookmark', 'creer')) { if (!preg_match('/bookmarks\/card.php/', $_SERVER['PHP_SELF'])) { - //$urltoadd=DOL_URL_ROOT.'/bookmarks/card.php?action=create&urlsource='.urlencode($url).'&url='.urlencode($url); - $urltoadd = DOL_URL_ROOT.'/bookmarks/card.php?action=create&url='.urlencode($url); + //$urltoadd=DOL_URL_ROOT.'/bookmarks/card.php?action=create&url='.urlencode($url); // With & the GETPOST('url') will fail. + $urltoadd = DOL_URL_ROOT.'/bookmarks/card.php?action=create&url='.urlencode($url); $newbtn .= ''; $newbtn .= img_picto('', 'add', '', 0, 0, 0, '', 'pictofixedwidth paddingright').dol_escape_htmltag($langs->trans('AddThisPageToBookmarks')).''; } diff --git a/htdocs/categories/class/categorie.class.php b/htdocs/categories/class/categorie.class.php index 58e9046c4d3..2cc989ecbf3 100644 --- a/htdocs/categories/class/categorie.class.php +++ b/htdocs/categories/class/categorie.class.php @@ -1526,15 +1526,15 @@ class Categorie extends CommonObject } if ($url == '') { - $link = ''; + $link = ''; $linkend = ''; $w[] = $link.(($addpicto && $i == 1) ? img_object('', 'category', 'class="paddingright"') : '').$cat->label.$linkend; } elseif ($url == 'none') { - $link = ''; + $link = ''; $linkend = ''; $w[] = $link.(($addpicto && $i == 1) ? img_object('', 'category', 'class="paddingright"') : '').$cat->label.$linkend; } else { - $w[] = ''.($addpicto ? img_object('', 'category') : '').$cat->label.''; + $w[] = ''.($addpicto ? img_object('', 'category') : '').$cat->label.''; } } $newcategwithpath = preg_replace('/colortoreplace/', $forced_color, implode(''.$sep.'', $w)); diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 8cd33589397..6655346de96 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -6941,7 +6941,7 @@ class Form * Else, default proposed VAT==0. End of rule. * @param bool $options_only Return HTML options lines only (for ajax treatment) * @param int<-1,1> $mode 0=Use vat rate as key in combo list, 1=Add VAT code after vat rate into key, -1=Use id of vat line as key - * @param int<0,2> $type_vat 0=All type, 1=VAT rate sale, 2=VAT rate purchase + * @param int<0,2> $type_vat 0=All types, 1=VAT rate for sales, 2=VAT rate for purchases * @return string */ public function load_tva($htmlname = 'tauxtva', $selectedrate = '', $societe_vendeuse = null, $societe_acheteuse = null, $idprod = 0, $info_bits = 0, $type = '', $options_only = false, $mode = 0, $type_vat = 0) @@ -7056,12 +7056,14 @@ class Form $num = count($arrayofvatrates); if ($num > 0) { - // Define vat rate to pre-select (if defaulttx not forced and so is -1 or '') - if (($defaulttx < 0 || dol_strlen($defaulttx) == 0) && is_object($societe_vendeuse)) { + // Define the vat rate to pre-select (if defaulttx not forced so is -1 or '') + if ($defaulttx < 0 || dol_strlen($defaulttx) == 0) { + // Define a default thirdparty to use if the seller or buyer is not defined $tmpthirdparty = new Societe($this->db); + $tmpthirdparty->country_code = $mysoc->country_code; - $defaulttx = get_default_tva($societe_vendeuse, (is_object($societe_acheteuse) ? $societe_acheteuse : $tmpthirdparty), $idprod); - $defaultnpr = get_default_npr($societe_vendeuse, (is_object($societe_acheteuse) ? $societe_acheteuse : $tmpthirdparty), $idprod); + $defaulttx = get_default_tva(is_object($societe_vendeuse) ? $societe_vendeuse : $tmpthirdparty, (is_object($societe_acheteuse) ? $societe_acheteuse : $tmpthirdparty), $idprod); + $defaultnpr = get_default_npr(is_object($societe_vendeuse) ? $societe_vendeuse : $tmpthirdparty, (is_object($societe_acheteuse) ? $societe_acheteuse : $tmpthirdparty), $idprod); if (preg_match('/\((.*)\)/', $defaulttx, $reg)) { $defaultcode = $reg[1]; @@ -9600,7 +9602,7 @@ class Form * * @param int $id Id of object * @param string $type Type of category ('member', 'customer', 'supplier', 'product', 'contact'). Old mode (0, 1, 2, ...) is deprecated. - * @param int<0,1> $rendermode 0=Default, use multiselect. 1=Emulate multiselect (recommended) + * @param int<0,1> $rendermode 0=Default, use multiselect (deprecated). 1=Emulate multiselect (recommended) * @param int<0,1> $nolink 1=Do not add html links * @return string String with categories */ diff --git a/htdocs/core/lib/files.lib.php b/htdocs/core/lib/files.lib.php index a77ac94e92b..529dc1c8d6e 100644 --- a/htdocs/core/lib/files.lib.php +++ b/htdocs/core/lib/files.lib.php @@ -2004,7 +2004,7 @@ function dol_add_file_process($upload_dir, $allowoverwrite = 0, $updatesessionor // Move file from temp directory to final directory. A .noexe may also be appended on file name. $resupload = dol_move_uploaded_file($TFile['tmp_name'][$i], $destfull, $allowoverwrite, 0, $TFile['error'][$i], 0, $varfiles, $upload_dir); - if (is_numeric($resupload) && $resupload > 0) { // $resupload can be 'ErrorFileAlreadyExists' + if (is_numeric($resupload) && $resupload > 0) { // $resupload can be 'ErrorFileAlreadyExists', 'ErrorFileIsInfectedWithAVirus...' include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php'; $tmparraysize = getDefaultImageSizes(); @@ -2068,7 +2068,7 @@ function dol_add_file_process($upload_dir, $allowoverwrite = 0, $updatesessionor if (preg_match('/File is a PDF with javascript inside/', $resupload)) { setEventMessages($langs->trans("ErrorFileIsAnInfectedPDFWithJSInside"), null, 'errors'); } else { - setEventMessages($langs->trans("ErrorFileIsInfectedWithAVirus"), null, 'errors'); + setEventMessages($langs->trans("ErrorFileIsInfectedWithAVirus").'
'.dolGetFirstLineOfText($resupload), null, 'errors'); } } else { // Known error setEventMessages($langs->trans($resupload), null, 'errors'); diff --git a/htdocs/core/lib/website.lib.php b/htdocs/core/lib/website.lib.php index 1590d5a0bf4..af2c363ffd6 100644 --- a/htdocs/core/lib/website.lib.php +++ b/htdocs/core/lib/website.lib.php @@ -286,12 +286,12 @@ function dolWebsiteOutput($content, $contenttype = 'html', $containerid = 0) global $db, $langs, $conf, $user; global $dolibarr_main_url_root, $dolibarr_main_data_root; global $website; - global $includehtmlcontentopened; + global $includehtmlcontentopened; // $includehtmlcontentopened is the level of includes (start at 0 for main page, 1 for first level include, ...) '@phan-var-force Website $website'; $nbrep = 0; - dol_syslog("dolWebsiteOutput start - contenttype=".$contenttype." containerid=".$containerid." USEDOLIBARREDITOR=".(defined('USEDOLIBARREDITOR') ? '1' : '')." USEDOLIBARRSERVER=".(defined('USEDOLIBARRSERVER') ? '1' : '').' includehtmlcontentopened='.$includehtmlcontentopened); + dol_syslog("dolWebsiteOutput start - contenttype=".$contenttype." containerid=".$containerid.(defined('USEDOLIBARREDITOR') ? ' USEDOLIBARREDITOR=1' : '').(defined('USEDOLIBARRSERVER') ? ' USEDOLIBARRSERVER=1' : '').' includehtmlcontentopened='.$includehtmlcontentopened); //print $containerid.' '.$content; @@ -1573,9 +1573,15 @@ function getAllImages($object, $objectpage, $urltograb, &$tmp, &$action, $modify dol_mkdir(dirname($filetosave)); $fp = fopen($filetosave, "w"); - fwrite($fp, $tmpgeturl['content']); - fclose($fp); - dolChmod($filetosave); + if ($fp) { + fwrite($fp, $tmpgeturl['content']); + fclose($fp); + dolChmod($filetosave); + } else { + $error++; + setEventMessages('Error failed to open file '.$filetosave.' for writing', null, 'errors'); + $action = 'create'; + } } } } diff --git a/htdocs/core/lib/website2.lib.php b/htdocs/core/lib/website2.lib.php index 5cf8ac7a361..36c81798d37 100644 --- a/htdocs/core/lib/website2.lib.php +++ b/htdocs/core/lib/website2.lib.php @@ -186,6 +186,9 @@ function dolSavePageContent($filetpl, Website $object, WebsitePage $objectpage, $tplcontent .= "} // Not already loaded\n"; $tplcontent .= "require_once DOL_DOCUMENT_ROOT.'/core/lib/website.lib.php';\n"; $tplcontent .= "require_once DOL_DOCUMENT_ROOT.'/core/website.inc.php';\n"; + if (in_array($objectpage->type_container, array('page', 'blogpost'))) { + $tplcontent .= 'dol_syslog("--- Prepare content of page '.((int) $objectpage->id).' - '.$objectpage->pageurl.'");'."\n"; + } $tplcontent .= "ob_start();\n"; $tplcontent .= "try {\n"; $tplcontent .= "// END PHP ?>\n"; @@ -375,6 +378,9 @@ function dolSavePageContent($filetpl, Website $object, WebsitePage $objectpage, $tplcontent .= "} // Not already loaded\n"; $tplcontent .= "require_once DOL_DOCUMENT_ROOT.'/core/lib/website.lib.php';\n"; $tplcontent .= "require_once DOL_DOCUMENT_ROOT.'/core/website.inc.php';\n"; + if (in_array($objectpage->type_container, array('page', 'blogpost'))) { + $tplcontent .= 'dol_syslog("--- Prepare content of page '.((int) $objectpage->id).' - '.$objectpage->pageurl.'");'."\n"; + } $tplcontent .= "// END PHP ?>\n"; $tplcontent .= $objectpage->content; diff --git a/htdocs/core/modules/member/mod_member_simple.php b/htdocs/core/modules/member/mod_member_simple.php index e35531f3265..cf5d55f2a49 100644 --- a/htdocs/core/modules/member/mod_member_simple.php +++ b/htdocs/core/modules/member/mod_member_simple.php @@ -92,6 +92,7 @@ class mod_member_simple extends ModeleNumRefMembers $sql = "SELECT MAX(CAST(ref AS SIGNED)) as max"; $sql .= " FROM ".MAIN_DB_PREFIX."adherent"; $sql .= " WHERE entity = ".$conf->entity; + $resql = $db->query($sql); if ($resql) { $row = $db->fetch_row($resql); @@ -125,6 +126,7 @@ class mod_member_simple extends ModeleNumRefMembers $sql = "SELECT MAX(CAST(ref AS SIGNED)) as max"; $sql .= " FROM ".MAIN_DB_PREFIX."adherent"; $sql .= " WHERE entity = ".(int) $conf->entity; + $sql .= " AND ref <> '(PROV)'"; $resql = $db->query($sql); if ($resql) { diff --git a/htdocs/core/triggers/interface_50_modTicket_TicketEmail.class.php b/htdocs/core/triggers/interface_50_modTicket_TicketEmail.class.php index 5f248d984ed..398f4e65351 100644 --- a/htdocs/core/triggers/interface_50_modTicket_TicketEmail.class.php +++ b/htdocs/core/triggers/interface_50_modTicket_TicketEmail.class.php @@ -1,11 +1,9 @@ +/* Copyright (C) 2014-2016 Jean-François Ferry * Copyright (C) 2016 Christophe Battarel - * Copyright (C) 2023 Benjamin Falière * Copyright (C) 2024-2025 MDW * Copyright (C) 2025 Frédéric France + * Copyright (C) 2023-2025 Benjamin Falière * * 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 @@ -502,10 +500,13 @@ class InterfaceTicketEmail extends DolibarrTriggers } $message_customer .= '

'.$langs->trans('Message').' :

'.$message.'


'; - $url_public_ticket = getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE', dol_buildpath('/public/ticket/', 2)).'view.php?track_id='.$object->track_id; - - $message_customer .= '

'.$langs->trans($see_ticket).' : '.$url_public_ticket.'

'; - $message_customer .= '

'.$langs->trans('TicketEmailPleaseDoNotReplyToThisEmail').'

'; + if (getDolGlobalInt('TICKET_ENABLE_PUBLIC_INTERFACE')) { + $url_public_ticket = getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE', dol_buildpath('/public/ticket/', 2)).'view.php?track_id='.((int) $object->track_id); + $message_customer .= '

'.$langs->trans($see_ticket).' : '.$url_public_ticket.'

'; + $message_customer .= '

'.$langs->trans('TicketEmailPleaseDoNotReplyToThisEmail').'

'; + } else { + $message_customer .= '

'.$langs->trans('TicketEmailPleaseDoNotReplyToThisEmailNoInterface').'

'; + } $from = (getDolGlobalString('MAIN_INFO_SOCIETE_NOM') ? getDolGlobalString('MAIN_INFO_SOCIETE_NOM') . ' ' : '').'<' . getDolGlobalString('TICKET_NOTIFICATION_EMAIL_FROM').'>'; diff --git a/htdocs/expensereport/card.php b/htdocs/expensereport/card.php index d931c854afb..850375d0ed6 100644 --- a/htdocs/expensereport/card.php +++ b/htdocs/expensereport/card.php @@ -474,10 +474,15 @@ if (empty($reshook)) { setEventMessages($mesg, null, 'mesgs'); } else { $langs->load("other"); - if ($mailfile->error) { + if (!empty($mailfile->error) || !empty($mailfile->errors)) { $mesg = ''; - $mesg .= $langs->trans('ErrorFailedToSendMail', $emailFrom, $emailTo); - $mesg .= '
'.$mailfile->error; + $mesg .= $langs->transnoentities('ErrorFailedToSendMail', dol_escape_htmltag($emailFrom), dol_escape_htmltag($emailTo)); + if (!empty($mailfile->error)) { + $mesg .= '
' . $mailfile->error; + } + if (!empty($mailfile->errors) && is_array($mailfile->errors)) { + $mesg .= '
' . implode('
', $mailfile->errors); + } setEventMessages($mesg, null, 'errors'); } else { setEventMessages('No mail sent. Feature is disabled by option MAIN_DISABLE_ALL_MAILS', null, 'warnings'); @@ -1553,7 +1558,7 @@ if ($action == 'create') { print $form->buttonsSaveCancel("AddTrip"); print ''; -} elseif ($id > 0 || $ref) { +} elseif ($object->id > 0) { $userauthor = null; $result = $object->fetch($id, $ref); @@ -2608,12 +2613,12 @@ if ($action == 'create') { // Select VAT print ''; - $defaultvat = -1; + $defaultvat = ''; if (getDolGlobalString('EXPENSEREPORT_NO_DEFAULT_VAT')) { // If option to have no default VAT on expense report is on, we force MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS $conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS = 'none'; } - print $form->load_tva('vatrate', (!empty($vatrate) ? $vatrate : $defaultvat), $mysoc, null, 0, 0, '', false, 1); + print $form->load_tva('vatrate', (!empty($vatrate) ? $vatrate : $defaultvat), null, null, 0, 0, '', false, 1); print ''; // Unit price net diff --git a/htdocs/langs/en_US/ticket.lang b/htdocs/langs/en_US/ticket.lang index 39b8ff61ce6..85bdd4c2bba 100644 --- a/htdocs/langs/en_US/ticket.lang +++ b/htdocs/langs/en_US/ticket.lang @@ -285,6 +285,7 @@ TicketNewEmailBodyInfosTrackUrl=You can view the progress of the ticket by click TicketNewEmailBodyInfosTrackUrlCustomer=You can view the progress of the ticket in the public ticket portal by clicking the following link TicketCloseEmailBodyInfosTrackUrlCustomer=You can consult the history of this ticket by clicking the following link TicketEmailPleaseDoNotReplyToThisEmail=Please do not reply directly to this email! Use the link to reply into the interface. +TicketEmailPleaseDoNotReplyToThisEmailNoInterface=Please do not reply directly to this email! TicketPublicInfoCreateTicket=This form allows you to record a support ticket in our management system. TicketPublicPleaseBeAccuratelyDescribe=Please accurately describe your request. Provide the most information possible to allow us to correctly identify your request. TicketPublicMsgViewLogIn=Please enter ticket tracking ID diff --git a/htdocs/langs/fr_FR/ticket.lang b/htdocs/langs/fr_FR/ticket.lang index 317bca11008..c025424a933 100644 --- a/htdocs/langs/fr_FR/ticket.lang +++ b/htdocs/langs/fr_FR/ticket.lang @@ -265,6 +265,7 @@ TicketLogAssignedTo=Ticket %s assigné à %s TicketLogPropertyChanged=Ticket %s modifié: classification de %s à %s TicketLogClosedBy=Ticket %s clôt par %s TicketLogReopen=Ticket %s ré-ouvert + # Public pages TicketSystem=Gestionnaire de tickets ShowListTicketWithTrackId=Afficher la liste des tickets à partir de l'ID de suivi @@ -283,6 +284,7 @@ TicketNewEmailBodyInfosTrackUrl=Vous pouvez voir la progression du ticket en cli TicketNewEmailBodyInfosTrackUrlCustomer=Vous pouvez visualiser la progression du ticket dans l'interface publique en cliquant sur le lien suivant TicketCloseEmailBodyInfosTrackUrlCustomer=Vous pouvez consulter l'historique de ce ticket en cliquant sur le lien suivant TicketEmailPleaseDoNotReplyToThisEmail=Merci de ne pas répondre directement à ce courriel ! Utilisez le lien pour répondre via l'interface. +TicketEmailPleaseDoNotReplyToThisEmailNoInterface=Merci de ne pas répondre directement à ce courriel ! TicketPublicInfoCreateTicket=Ce formulaire vous permet d'enregistrer un ticket dans notre système de gestion. TicketPublicPleaseBeAccuratelyDescribe=Veuillez décrire précisément votre question. Fournissez le plus d'informations possible pour nous permettre d'identifier correctement votre demande. TicketPublicMsgViewLogIn=Merci d'entrer le code de suivi du ticket