diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 446a9e0d22f..2ca9ba03318 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -26,6 +26,7 @@ * Copyright (C) 2024 Josep Lluís Amador Teruel * Copyright (C) 2024 Benoît PASCAL * Copyright (C) 2025 Vincent Maury + * Copyright (C) 2026 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 @@ -16723,10 +16724,11 @@ function show_actions_messaging($conf, $langs, $db, $filterobj, $objcon = null, } $libelle = ''; - if (!empty($actionstatic->code) && preg_match('/^TICKET_MSG/', $actionstatic->code)) { + + if (!empty($actionstatic->code) && preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) { + $out .= $langs->trans('TicketNewMessage').' ('.$langs->trans('Private').')'; + } elseif (!empty($actionstatic->code) && preg_match('/^TICKET_MSG/', $actionstatic->code)) { $out .= $langs->trans('TicketNewMessage'); - } elseif (!empty($actionstatic->code) && preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) { - $out .= $langs->trans('TicketNewMessage') . ' (' . $langs->trans('Private') . ')'; } elseif (isset($histo[$key]['type'])) { if ($histo[$key]['type'] == 'action') { $transcode = $langs->transnoentitiesnoconv("Action" . $histo[$key]['acode']); diff --git a/htdocs/core/lib/functions2.lib.php b/htdocs/core/lib/functions2.lib.php index 56684544782..ea13fa69549 100644 --- a/htdocs/core/lib/functions2.lib.php +++ b/htdocs/core/lib/functions2.lib.php @@ -2658,7 +2658,7 @@ function getModuleDirForApiClass($moduleobject) $moduledirforclass = 'api'; } elseif (in_array($moduleobject, ['contact', 'contacts', 'customer', 'thirdparty', 'thirdparties'])) { $moduledirforclass = 'societe'; - } elseif ($moduleobject == 'propale' || $moduleobject == 'proposals') { + } elseif ($moduleobject == 'propal' || $moduleobject == 'propale' || $moduleobject == 'proposals') { $moduledirforclass = 'comm/propal'; } elseif ($moduleobject == 'agenda' || $moduleobject == 'agendaevents') { $moduledirforclass = 'comm/action'; diff --git a/htdocs/core/modules/expedition/doc/pdf_espadon.modules.php b/htdocs/core/modules/expedition/doc/pdf_espadon.modules.php index 949e3b115dd..0ec28ddb60a 100644 --- a/htdocs/core/modules/expedition/doc/pdf_espadon.modules.php +++ b/htdocs/core/modules/expedition/doc/pdf_espadon.modules.php @@ -737,6 +737,11 @@ class pdf_espadon extends ModelePdfExpedition $nexY = max($pdf->GetY(), $nexY); } + if ($this->getColumnStatus('totalexcltax') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $this->printStdColumnContent($pdf, $curY, 'totalexcltax', price($object->lines[$i]->total_ht, 0, $outputlangs)); + $nexY = max($pdf->GetY(), $nexY); + } + // Extrafields if (!empty($object->lines[$i]->array_options) && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { foreach ($object->lines[$i]->array_options as $extrafieldColKey => $extrafieldValue) { @@ -954,8 +959,8 @@ class pdf_espadon extends ModelePdfExpedition $this->printStdColumnContent($pdf, $tab2_top, 'qty_shipped', $totalToShip); } - if ($this->getColumnStatus('subprice')) { - $this->printStdColumnContent($pdf, $tab2_top, 'subprice', price($object->total_ht, 0, $outputlangs)); + if ($this->getColumnStatus('totalexcltax')) { + $this->printStdColumnContent($pdf, $tab2_top, 'totalexcltax', price($object->total_ht, 0, $outputlangs)); } $pdf->SetTextColor(0, 0, 0); diff --git a/htdocs/expedition/card.php b/htdocs/expedition/card.php index 58299a8a852..303e1c41f8f 100644 --- a/htdocs/expedition/card.php +++ b/htdocs/expedition/card.php @@ -1959,7 +1959,7 @@ if ($action == 'create' && $usercancreate) { if ($res < 0) { dol_print_error($db, $product->error, $product->errors); } - if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) { + if (getDolGlobalInt('PRODUIT_SOUSPRODUITS') && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) { $productChildrenNb = $product->hasFatherOrChild(1); } if ($productChildrenNb > 0) { @@ -2168,13 +2168,13 @@ if ($action == 'create' && $usercancreate) { $subj = 0; // Define nb of lines suggested for this order line $nbofsuggested = 0; - if (is_object($product->stock_warehouse[$warehouse_id]) && count($product->stock_warehouse[$warehouse_id]->detail_batch)) { + if (is_object($product->stock_warehouse[$warehouse_id]) && !empty($product->stock_warehouse[$warehouse_id]->detail_batch)) { foreach ($product->stock_warehouse[$warehouse_id]->detail_batch as $dbatch) { $nbofsuggested++; } } - print ''; - if (is_object($product->stock_warehouse[$warehouse_id]) && count($product->stock_warehouse[$warehouse_id]->detail_batch)) { + print ''; + if (is_object($product->stock_warehouse[$warehouse_id]) && !empty($product->stock_warehouse[$warehouse_id]->detail_batch)) { foreach ($product->stock_warehouse[$warehouse_id]->detail_batch as $dbatch) { // $dbatch is instance of Productbatch //var_dump($dbatch); $batchStock = +$dbatch->qty; // To get a numeric @@ -2414,7 +2414,7 @@ if ($action == 'create' && $usercancreate) { // Define nb of lines suggested for this order line $nbofsuggested = 0; foreach ($product->stock_warehouse as $warehouse_id => $stock_warehouse) { - if (($stock_warehouse->real > 0 || !getDolGlobalInt('STOCK_DISALLOW_NEGATIVE_TRANSFER')) && (count($stock_warehouse->detail_batch))) { + if (($stock_warehouse->real > 0 || !getDolGlobalInt('STOCK_DISALLOW_NEGATIVE_TRANSFER')) && (!empty($stock_warehouse->detail_batch))) { $nbofsuggested += count($stock_warehouse->detail_batch); } } @@ -2427,7 +2427,7 @@ if ($action == 'create' && $usercancreate) { } $tmpwarehouseObject->fetch($warehouse_id); - if (($stock_warehouse->real > 0 || !getDolGlobalInt('STOCK_DISALLOW_NEGATIVE_TRANSFER')) && (count($stock_warehouse->detail_batch))) { + if (($stock_warehouse->real > 0 || !getDolGlobalInt('STOCK_DISALLOW_NEGATIVE_TRANSFER')) && (!empty($stock_warehouse->detail_batch))) { foreach ($stock_warehouse->detail_batch as $dbatch) { $batchStock = +$dbatch->qty; // To get a numeric if (isset($alreadyQtyBatchSetted[$line->fk_product][$dbatch->batch][intval($warehouse_id)])) { diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 65d03fdd33f..4896d1c0890 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -569,9 +569,9 @@ class Expedition extends CommonObject if (empty($line->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) { $line_id = 0; if (!isset($kits_id_cached[$line->fk_product])) { - if (!isset($line->detail_batch) || isset($kits_list[$line->fk_product])) { // no batch management or is kit + if (!isset($line->detail_batch) || (isset($kits_list[$line->fk_product]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE'))) { // no batch management or is kit $qty = isset($kits_list[$line->fk_product]) ? $kits_list[$line->fk_product]['total_qty'] : $line->qty; - $warehouse_id = isset($kits_list[$line->fk_product]) ? 0 : $line->entrepot_id; + $warehouse_id = (isset($kits_list[$line->fk_product]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) ? 0 : $line->entrepot_id; $line_id = $this->create_line($warehouse_id, $line->origin_line_id, $qty, $line->rang, $line->array_options, 0, $line->fk_product); if ($line_id <= 0) { $error++; @@ -589,7 +589,7 @@ class Expedition extends CommonObject } // virtual products - if (isset($kits_list[$line->fk_product])) { + if (isset($kits_list[$line->fk_product]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) { $prods_arbo = $kits_list[$line->fk_product]['arbo']; $total_qty = $kits_list[$line->fk_product]['total_qty']; @@ -650,7 +650,7 @@ class Expedition extends CommonObject // create line for a child of virtual product if (!isset($sub_kits_id_cached[$product_child_id]) || $warehouse_id > 0) { - $line_id = $this->create_line($warehouse_id, 0, $product_child_qty, $line->rang, $line->array_options, $parent_line_id, $product_child_id); + $line_id = $this->create_line($warehouse_id, ($parent_line_id ? 0 : $line->origin_line_id), $product_child_qty, $line->rang, $line->array_options, $parent_line_id, $product_child_id); if ($line_id <= 0) { $error++; dol_syslog(__METHOD__ . ' : ' . $this->errorsToString(), LOG_ERR); @@ -1749,7 +1749,7 @@ class Expedition extends CommonObject $obj = $this->db->fetch_object($resql); $line_id = (int) $obj->expeditiondet_id; - if ($can_update_stock && empty($obj->iskit) && !empty($obj->incdec)) { + if ($can_update_stock && (empty($obj->iskit) || getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) && !empty($obj->incdec)) { $mouvS = new MouvementStock($this->db); // we do not log origin because it will be deleted $mouvS->origin = ''; @@ -1954,7 +1954,7 @@ class Expedition extends CommonObject $obj = $this->db->fetch_object($resql); $line_id = (int) $obj->expeditiondet_id; - if ($can_update_stock && empty($obj->iskit) && !empty($obj->incdec)) { + if ($can_update_stock && (empty($obj->iskit) || getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) && !empty($obj->incdec)) { $mouvS = new MouvementStock($this->db); // we do not log origin because it will be deleted $mouvS->origin = ''; diff --git a/htdocs/expedition/dispatch.php b/htdocs/expedition/dispatch.php index c061588759a..a1dcaa6782c 100644 --- a/htdocs/expedition/dispatch.php +++ b/htdocs/expedition/dispatch.php @@ -913,7 +913,9 @@ if ($object->id > 0 || !empty($object->ref)) { $resql_child = $db->query($sql_child); if ($resql_child) { if ($child_obj = $db->fetch_object($resql_child)) { - $line_obj->iskit = (int) $child_obj->iskit; + if (!getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) { + $line_obj->iskit = (int) $child_obj->iskit; + } if ($can_manage_stock) { $line_obj->incdec = (int) $child_obj->incdec; // reset value to 0 or 1 if stock can be managed } diff --git a/htdocs/includes/odtphp/odf.php b/htdocs/includes/odtphp/odf.php index 9b7ce2eaf10..cd8efcd481d 100644 --- a/htdocs/includes/odtphp/odf.php +++ b/htdocs/includes/odtphp/odf.php @@ -99,7 +99,7 @@ class Odf * Class constructor * * @param string $filename The name of the odt file - * @param array $config Array of config data + * @param array $config Array of config data * @throws OdfException */ public function __construct($filename, $config = array()) @@ -957,19 +957,32 @@ IMG; // $result = $utils->executeCLI($command, $outputfile); and replace test on $execmethod. // $retval will be $result['result'] // $errorstring will be $result['output'] + + // Protect parentheses from being double-escaped by escapeshellcmd(). + // The $command is already built with escapeshellarg() for all arguments, + // but escapeshellcmd() escapes parentheses inside quoted strings, breaking + // filenames like "(PROV35)_invoice.odt" for draft invoices. + // Security: reject if placeholder strings already exist to prevent injection. + if (strpos($command, '__PARENTHESIS_OPEN__') !== false || strpos($command, '__PARENTHESIS_CLOSE__') !== false) { + dol_syslog(get_class($this).'::exportAsAttachedPDF Invalid characters in command path: '.$command, LOG_WARNING); + throw new OdfException('Invalid characters in command path'); + } + $commandprotected = str_replace(array('(', ')'), array('__PARENTHESIS_OPEN__', '__PARENTHESIS_CLOSE__'), $command); + $commandescaped = escapeshellcmd($commandprotected); + $commandescapedtoexec = str_replace(array('__PARENTHESIS_OPEN__', '__PARENTHESIS_CLOSE__'), array('(', ')'), $commandescaped); + $retval=0; $output_arr=array(); if ($execmethod == 1) { - exec(escapeshellcmd($command), $output_arr, $retval); - } - if ($execmethod == 2) { + exec($commandescapedtoexec, $output_arr, $retval); + } elseif ($execmethod == 2) { $outputfile = DOL_DATA_ROOT.'/odt2pdf.log'; $handle = fopen($outputfile, 'w'); if ($handle) { dol_syslog(get_class($this)."Run command ".$command, LOG_DEBUG); - dol_syslog(get_class($this)."escapeshellcmd(command) = ".escapeshellcmd($command), LOG_DEBUG); + dol_syslog(get_class($this)."escapeshellcmd(command) = ".$commandescapedtoexec, LOG_DEBUG); fwrite($handle, $command."\n"); - $handlein = popen(escapeshellcmd($command), 'r'); + $handlein = popen($commandescapedtoexec, 'r'); while (!feof($handlein)) { $read = fgets($handlein); fwrite($handle, $read);