From 8ddf9f6075e12b7754248741bd549092bb2b22d0 Mon Sep 17 00:00:00 2001 From: tnegre Date: Thu, 16 Oct 2025 11:41:52 +0200 Subject: [PATCH 001/172] FIX : remove useless condition to create credit on situation invoice (#35786) --- htdocs/compta/facture/card.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/htdocs/compta/facture/card.php b/htdocs/compta/facture/card.php index 39c271ea0ca4e..007e2c89d2dce 100644 --- a/htdocs/compta/facture/card.php +++ b/htdocs/compta/facture/card.php @@ -5796,10 +5796,9 @@ function js_recalculate_revenuestamp(){ } } - // For situation invoice with excess received + // For situation invoice if ($object->statut > Facture::STATUS_DRAFT && $object->type == Facture::TYPE_SITUATION - && ($object->total_ttc - $totalpaid - $totalcreditnotes - $totaldeposits) > 0 && $usercancreate && !$objectidnext && $object->is_last_in_cycle() From 52446678d083da7eee86f27dfc374ff85fc16968 Mon Sep 17 00:00:00 2001 From: Ryad ABANI Date: Mon, 20 Oct 2025 16:23:22 +0200 Subject: [PATCH 002/172] FIX: php8.1 warning in syslog message --- htdocs/core/class/CMailFile.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/class/CMailFile.class.php b/htdocs/core/class/CMailFile.class.php index f6897b6a2d1b5..18424785ffecf 100644 --- a/htdocs/core/class/CMailFile.class.php +++ b/htdocs/core/class/CMailFile.class.php @@ -319,7 +319,7 @@ public function __construct($subject, $to, $from, $msg, $filename_list = array() foreach ($filename_list as $i => $val) { if ($filename_list[$i]) { $this->atleastonefile = 1; - dol_syslog("CMailFile::CMailfile: filename_list[$i]=".$filename_list[$i].", mimetype_list[$i]=".$mimetype_list[$i]." mimefilename_list[$i]=".$mimefilename_list[$i]." cid_list[$i]=".$cid_list[$i], LOG_DEBUG); + dol_syslog("CMailFile::CMailfile: filename_list[$i]=".$filename_list[$i].", mimetype_list[$i]=".$mimetype_list[$i]." mimefilename_list[$i]=".$mimefilename_list[$i]. (empty($cid_list[$i]) ? 'Not an image' : $cid_list[$i]), LOG_DEBUG); } } } From 2153dbb0f175e62eee0bd4815cf829c29c476eae Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 21 Oct 2025 02:09:51 +0200 Subject: [PATCH 003/172] Update CMailFile.class.php --- htdocs/core/class/CMailFile.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/class/CMailFile.class.php b/htdocs/core/class/CMailFile.class.php index 18424785ffecf..5ccd242bf1f6a 100644 --- a/htdocs/core/class/CMailFile.class.php +++ b/htdocs/core/class/CMailFile.class.php @@ -319,7 +319,7 @@ public function __construct($subject, $to, $from, $msg, $filename_list = array() foreach ($filename_list as $i => $val) { if ($filename_list[$i]) { $this->atleastonefile = 1; - dol_syslog("CMailFile::CMailfile: filename_list[$i]=".$filename_list[$i].", mimetype_list[$i]=".$mimetype_list[$i]." mimefilename_list[$i]=".$mimefilename_list[$i]. (empty($cid_list[$i]) ? 'Not an image' : $cid_list[$i]), LOG_DEBUG); + dol_syslog("CMailFile::CMailfile: filename_list[$i]=".$filename_list[$i].", mimetype_list[$i]=".$mimetype_list[$i]." mimefilename_list[$i]=".$mimefilename_list[$i]. (empty($cid_list[$i]) ? 'Not an embedded image' : $cid_list[$i]), LOG_DEBUG); } } } From 1eed0e18a9581318f7071ff1e3d061f0f6a82089 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 21 Oct 2025 02:11:56 +0200 Subject: [PATCH 004/172] Update CMailFile.class.php --- htdocs/core/class/CMailFile.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/class/CMailFile.class.php b/htdocs/core/class/CMailFile.class.php index 5ccd242bf1f6a..9e096d27f641c 100644 --- a/htdocs/core/class/CMailFile.class.php +++ b/htdocs/core/class/CMailFile.class.php @@ -319,7 +319,7 @@ public function __construct($subject, $to, $from, $msg, $filename_list = array() foreach ($filename_list as $i => $val) { if ($filename_list[$i]) { $this->atleastonefile = 1; - dol_syslog("CMailFile::CMailfile: filename_list[$i]=".$filename_list[$i].", mimetype_list[$i]=".$mimetype_list[$i]." mimefilename_list[$i]=".$mimefilename_list[$i]. (empty($cid_list[$i]) ? 'Not an embedded image' : $cid_list[$i]), LOG_DEBUG); + dol_syslog("CMailFile::CMailfile: filename_list[$i]=".$filename_list[$i].", mimetype_list[$i]=".$mimetype_list[$i]." mimefilename_list[$i]=".$mimefilename_list[$i]." cid_list[$i]=".(empty($cid_list[$i]) ? 'Not an embedded image' : $cid_list[$i]), LOG_DEBUG); } } } From 71ec381d3e4b6da13503f257aa51ec6e4e80494b Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Mon, 10 Nov 2025 10:56:07 +0100 Subject: [PATCH 005/172] FIX re-create API temp dir after purging temp files --- htdocs/core/class/utils.class.php | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/htdocs/core/class/utils.class.php b/htdocs/core/class/utils.class.php index 285b88cd52ad2..b7a851aaa3073 100644 --- a/htdocs/core/class/utils.class.php +++ b/htdocs/core/class/utils.class.php @@ -168,8 +168,25 @@ public function purgeFiles($choices = 'tempfilesold+logfiles', $nbsecondsold = 8 } // Recreate temp dir that are not automatically recreated by core code for performance purpose, we need them - if (!empty($conf->api->enabled)) { - dol_mkdir($conf->api->dir_temp); + if (isModEnabled('api')) { + if (isModEnabled('multicompany')) { + global $mc; + + if (is_object($mc)) { + $entitiesList = $mc->getEntitiesList(); + foreach ($entitiesList as $entityId => $entity) { + if ($entityId > 1) { + $apiDir = DOL_DATA_ROOT.'/'.$entityId.'/api'; + if (is_dir($apiDir)) { // only create API temp directory of entity (can create routes.php file from Restler API) if Restler API is enabled (API directory exists) + $apiTempDir = DOL_DATA_ROOT.'/'.$entityId.'/api/temp/'; + dol_mkdir($apiTempDir); + } + } + } + } + } + + dol_mkdir($conf->api->dir_temp); // create API temp directory for main entity (can create routes.php file from Restler API) } dol_mkdir($conf->user->dir_temp); From c2ae7d2da1d6d5d925184ac4314d85ba50ee74b1 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Thu, 13 Nov 2025 14:01:34 +0100 Subject: [PATCH 006/172] FIX create API temp directory in Dolibarr API constructor --- htdocs/api/class/api.class.php | 12 ++++++++++++ htdocs/core/class/utils.class.php | 19 +------------------ 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/htdocs/api/class/api.class.php b/htdocs/api/class/api.class.php index e4f093be87f21..ae9db36974431 100644 --- a/htdocs/api/class/api.class.php +++ b/htdocs/api/class/api.class.php @@ -58,6 +58,18 @@ public function __construct($db, $cachedir = '', $refreshCache = false) $this->db = $db; $production_mode = (empty($conf->global->API_PRODUCTION_MODE) ? false : true); + + if ($production_mode) { + // Create the directory Defaults::$cacheDirectory if it does not exist. If dir does not exist, using production_mode generates an error 500. + include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; + if (!dol_is_dir(Defaults::$cacheDirectory)) { + dol_mkdir(Defaults::$cacheDirectory, DOL_DATA_ROOT); + } + if (getDolGlobalString('MAIN_API_DEBUG')) { + dol_syslog("Debug API construct::cacheDirectory=".Defaults::$cacheDirectory, LOG_DEBUG, 0, '_api'); + } + } + $this->r = new Restler($production_mode, $refreshCache); $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root)); diff --git a/htdocs/core/class/utils.class.php b/htdocs/core/class/utils.class.php index b7a851aaa3073..dc4174eb3aabe 100644 --- a/htdocs/core/class/utils.class.php +++ b/htdocs/core/class/utils.class.php @@ -169,24 +169,7 @@ public function purgeFiles($choices = 'tempfilesold+logfiles', $nbsecondsold = 8 // Recreate temp dir that are not automatically recreated by core code for performance purpose, we need them if (isModEnabled('api')) { - if (isModEnabled('multicompany')) { - global $mc; - - if (is_object($mc)) { - $entitiesList = $mc->getEntitiesList(); - foreach ($entitiesList as $entityId => $entity) { - if ($entityId > 1) { - $apiDir = DOL_DATA_ROOT.'/'.$entityId.'/api'; - if (is_dir($apiDir)) { // only create API temp directory of entity (can create routes.php file from Restler API) if Restler API is enabled (API directory exists) - $apiTempDir = DOL_DATA_ROOT.'/'.$entityId.'/api/temp/'; - dol_mkdir($apiTempDir); - } - } - } - } - } - - dol_mkdir($conf->api->dir_temp); // create API temp directory for main entity (can create routes.php file from Restler API) + dol_mkdir($conf->api->dir_temp); } dol_mkdir($conf->user->dir_temp); From 9319059349ac4cb8fb916b004cd3936977c26ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9?= Date: Thu, 20 Nov 2025 14:01:29 +0100 Subject: [PATCH 007/172] FIX: Sometimes socid = undefined --- htdocs/takepos/index.php | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/htdocs/takepos/index.php b/htdocs/takepos/index.php index fe9c34946a426..3ae0da797c444 100644 --- a/htdocs/takepos/index.php +++ b/htdocs/takepos/index.php @@ -93,6 +93,8 @@ $MAXCATEG = (empty($conf->global->TAKEPOS_NB_MAXCATEG) ? $maxcategbydefaultforthisdevice : $conf->global->TAKEPOS_NB_MAXCATEG); $MAXPRODUCT = (empty($conf->global->TAKEPOS_NB_MAXPRODUCT) ? $maxproductbydefaultforthisdevice : $conf->global->TAKEPOS_NB_MAXPRODUCT); +$term = empty($_SESSION['takeposterminal']) ? 1 : $_SESSION['takeposterminal']; +$socid = getDolGlobalString('CASHDESK_ID_THIRDPARTY' . $term); /* $constforcompanyid = 'CASHDESK_ID_THIRDPARTY'.$_SESSION["takeposterminal"]; $soc = new Societe($db); @@ -329,8 +331,15 @@ function LoadProducts(position, issubcat) { if (maxproduct >= 1) { limit = maxproduct-1; } + + // Get socid + let socid = jQuery('#thirdpartyid').val(); + if ((socid === undefined || socid === "") && parseInt("") > 0) { + socid = parseInt(""); + } + // Only show products for sale (tosell=1) - $.getJSON('/takepos/ajax/ajax.php?action=getProducts&token=&thirdpartyid=' + jQuery('#thirdpartyid').val() + '&category='+currentcat+'&tosell=1&limit='+limit+'&offset=0', function(data) { + $.getJSON('/takepos/ajax/ajax.php?action=getProducts&token=&thirdpartyid=' + socid + '&category='+currentcat+'&tosell=1&limit='+limit+'&offset=0', function(data) { console.log("Call ajax.php (in LoadProducts) to get Products of category "+currentcat+" then loop on result to fill image thumbs"); console.log(data); while (ishow < maxproduct) { @@ -437,8 +446,15 @@ function MoreProducts(moreorless) { limit = maxproduct-1; } var offset = * pageproducts; + + // Get socid + let socid = jQuery('#thirdpartyid').val(); + if ((socid === undefined || socid === "") && parseInt("") > 0) { + socid = parseInt(""); + } + // Only show products for sale (tosell=1) - $.getJSON('/takepos/ajax/ajax.php?action=getProducts&token=&thirdpartyid=' + jQuery('#thirdpartyid').val() + '&category='+currentcat+'&tosell=1&limit='+limit+'&offset='+offset, function(data) { + $.getJSON('/takepos/ajax/ajax.php?action=getProducts&token=&thirdpartyid=' + socid + '&category='+currentcat+'&tosell=1&limit='+limit+'&offset='+offset, function(data) { console.log("Call ajax.php (in MoreProducts) to get Products of category "+currentcat); if (typeof (data[0]) == "undefined" && moreorless=="more"){ // Return if no more pages @@ -662,7 +678,14 @@ function Search2(keyCodeForEnter, moreorless) { pageproducts = 0; jQuery(".wrapper2 .catwatermark").hide(); var nbsearchresults = 0; - $.getJSON('/takepos/ajax/ajax.php?action=search&token=&term=' + search_term + '&thirdpartyid=' + jQuery('#thirdpartyid').val() + '&search_start=' + search_start + '&search_limit=' + search_limit, function (data) { + + // Only show products for sale (tosell=1) + let socid = jQuery('#thirdpartyid').val(); + if ((socid === undefined || socid === "") && parseInt("") > 0) { + socid = parseInt(""); + } + + $.getJSON('/takepos/ajax/ajax.php?action=search&token=&term=' + search_term + '&thirdpartyid=' + socid + '&search_start=' + search_start + '&search_limit=' + search_limit, function (data) { for (i = 0; i < ; i++) { if (typeof (data[i]) == "undefined") { $("#prowatermark" + i).html(""); From 92197cb100c5cdce53b17cba22e66ec5f6516cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9?= Date: Fri, 21 Nov 2025 16:06:09 +0100 Subject: [PATCH 008/172] FIX: php echo instead of ") > 0) { + if ((socid === undefined || socid === "") && parseInt("") > 0) { socid = parseInt(""); } @@ -449,7 +449,7 @@ function MoreProducts(moreorless) { // Get socid let socid = jQuery('#thirdpartyid').val(); - if ((socid === undefined || socid === "") && parseInt("") > 0) { + if ((socid === undefined || socid === "") && parseInt("") > 0) { socid = parseInt(""); } @@ -681,7 +681,7 @@ function Search2(keyCodeForEnter, moreorless) { // Only show products for sale (tosell=1) let socid = jQuery('#thirdpartyid').val(); - if ((socid === undefined || socid === "") && parseInt("") > 0) { + if ((socid === undefined || socid === "") && parseInt("") > 0) { socid = parseInt(""); } From 1cd3ca32222880242d94530a9d9d4a605de4ad32 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Mon, 24 Nov 2025 10:30:08 +0100 Subject: [PATCH 009/172] FIX undefined variables on create invoice from shipment card (backport from v20) --- htdocs/compta/facture/card.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/htdocs/compta/facture/card.php b/htdocs/compta/facture/card.php index 5aa884f61f58f..9ce5fd95f986e 100644 --- a/htdocs/compta/facture/card.php +++ b/htdocs/compta/facture/card.php @@ -1710,25 +1710,25 @@ // Date start $date_start = false; - if ($lines[$i]->date_debut_prevue) { + if (isset($lines[$i]->date_debut_prevue)) { $date_start = $lines[$i]->date_debut_prevue; } - if ($lines[$i]->date_debut_reel) { + if (isset($lines[$i]->date_debut_reel)) { $date_start = $lines[$i]->date_debut_reel; } - if ($lines[$i]->date_start) { + if (isset($lines[$i]->date_start)) { $date_start = $lines[$i]->date_start; } // Date end $date_end = false; - if ($lines[$i]->date_fin_prevue) { + if (isset($lines[$i]->date_fin_prevue)) { $date_end = $lines[$i]->date_fin_prevue; } - if ($lines[$i]->date_fin_reel) { + if (isset($lines[$i]->date_fin_reel)) { $date_end = $lines[$i]->date_fin_reel; } - if ($lines[$i]->date_end) { + if (isset($lines[$i]->date_end)) { $date_end = $lines[$i]->date_end; } From 7ed0af2a138a34e7c7005b95c85ffc791976a6cf Mon Sep 17 00:00:00 2001 From: Florian Mortgat <50440633+atm-florianm@users.noreply.github.com> Date: Wed, 19 Nov 2025 19:19:55 +0100 Subject: [PATCH 010/172] FIX DA027383: permissions not checked on HRM evaluation card (#36328) Permissions involved: - hrm->evaluation->readall: the user can view anyone's evaluations - hrm->evaluation->read: the user can only view their or their subordinates' evaluations --- htdocs/core/lib/security.lib.php | 20 +++++++++++++++++++- htdocs/hrm/evaluation_card.php | 4 ++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/htdocs/core/lib/security.lib.php b/htdocs/core/lib/security.lib.php index c29c0df712636..19768625ec1ec 100644 --- a/htdocs/core/lib/security.lib.php +++ b/htdocs/core/lib/security.lib.php @@ -401,6 +401,10 @@ function restrictedArea(User $user, $features, $object = 0, $tableandshare = '', $tableandshare = 'paiementcharge'; $parentfortableentity = 'fk_charge@chargesociales'; } + if ($features == 'evaluation') { + $features = 'hrm'; + $feature2 = 'evaluation'; + } // Get more permissions checks from hooks $parameters = array('features'=>$features, 'originalfeatures'=>$originalfeatures, 'objectid'=>$objectid, 'dbt_select'=>$dbt_select, 'idtype'=>$dbt_select, 'isdraft'=>$isdraft); @@ -834,7 +838,7 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl $checkother = array('contact', 'agenda', 'contrat'); // Test on entity + link to third party on field $dbt_keyfield. Allowed if link is empty (Ex: contacts...). $checkproject = array('projet', 'project'); // Test for project object $checktask = array('projet_task'); // Test for task object - $checkhierarchy = array('expensereport', 'holiday'); // check permission among the hierarchy of user + $checkhierarchy = array('expensereport', 'holiday', 'hrm'); // check permission among the hierarchy of user $checkuser = array('bookmark'); // check permission among the fk_user (must be myself or null) $nocheck = array('barcode', 'stock'); // No test @@ -1055,6 +1059,20 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl } } } + if ($feature == 'hrm' && in_array('evaluation', $feature2)) { + $useridtocheck = $object->fk_user; + + if ($user->hasRight('hrm', 'evaluation', 'readall')) { + // the user can view evaluations for anyone + return true; + } + if (!$user->hasRight('hrm', 'evaluation', 'read')) { + // the user can't view any evaluations + return false; + } + // the user can only their own evaluations or their subordinates' + return in_array($useridtocheck, $childids); + } } // For some object, we also have to check it is public or owned by user diff --git a/htdocs/hrm/evaluation_card.php b/htdocs/hrm/evaluation_card.php index e59ba7db462e3..97c77cbd76ced 100644 --- a/htdocs/hrm/evaluation_card.php +++ b/htdocs/hrm/evaluation_card.php @@ -92,8 +92,8 @@ // Security check (enable the most restrictive one) //if ($user->socid > 0) accessforbidden(); //if ($user->socid > 0) $socid = $user->socid; -//$isdraft = (($object->status == $object::STATUS_DRAFT) ? 1 : 0); -//restrictedArea($user, $object->element, $object->id, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); +$isdraft = ($object->status == Evaluation::STATUS_DRAFT) ? 1 : 0; +restrictedArea($user, $object->element, $object, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); if (!isModEnabled("hrm")) { accessforbidden(); } From c1c2358e75418d494a527c245489a488aad5f93c Mon Sep 17 00:00:00 2001 From: Adrien Raze Date: Wed, 26 Nov 2025 12:30:22 +0100 Subject: [PATCH 011/172] FIX : Implementation of multi-company compatibility with inventory/warehouse management --- htdocs/core/lib/security.lib.php | 4 ++-- htdocs/product/inventory/card.php | 4 ++-- htdocs/product/inventory/inventory.php | 4 ++-- htdocs/product/stock/card.php | 3 +-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/htdocs/core/lib/security.lib.php b/htdocs/core/lib/security.lib.php index 2d46f6b3c8ba9..cdceab83dc12b 100644 --- a/htdocs/core/lib/security.lib.php +++ b/htdocs/core/lib/security.lib.php @@ -902,14 +902,14 @@ function checkUserAccessToObject($user, array $featuresarray, $object = 0, $tabl $checkonentitydone = 0; // Array to define rules of checks to do - $check = array('adherent', 'banque', 'bom', 'don', 'mrp', 'user', 'usergroup', 'payment', 'payment_supplier', 'payment_sc', 'product', 'produit', 'service', 'produit|service', 'categorie', 'resource', 'expensereport', 'holiday', 'salaries', 'website', 'recruitment', 'chargesociales', 'knowledgemanagement'); // Test on entity only (Objects with no link to company) + $check = array('adherent', 'banque', 'bom', 'don', 'mrp', 'user', 'usergroup', 'payment', 'payment_supplier', 'payment_sc', 'product', 'produit', 'service', 'produit|service', 'categorie', 'resource', 'expensereport', 'holiday', 'salaries', 'website', 'recruitment', 'chargesociales', 'knowledgemanagement', 'stock'); // Test on entity only (Objects with no link to company) $checksoc = array('societe'); // Test for object Societe $checkparentsoc = array('agenda', 'contact', 'contrat'); // Test on entity + link to third party on field $dbt_keyfield. Allowed if link is empty (Ex: contacts...). $checkproject = array('projet', 'project'); // Test for project object $checktask = array('projet_task', 'project_task'); // Test for task object $checkhierarchy = array('expensereport', 'holiday', 'hrm'); // check permission among the hierarchy of user $checkuser = array('bookmark'); // check permission among the fk_user (must be myself or null) - $nocheck = array('barcode', 'stock'); // No test + $nocheck = array('barcode'); // No test //$checkdefault = 'all other not already defined'; // Test on entity + link to third party on field $dbt_keyfield. Not allowed if link is empty (Ex: invoice, orders...). diff --git a/htdocs/product/inventory/card.php b/htdocs/product/inventory/card.php index 79d7b21757e3d..37285af197428 100644 --- a/htdocs/product/inventory/card.php +++ b/htdocs/product/inventory/card.php @@ -42,9 +42,9 @@ $backtopage = GETPOST('backtopage', 'alpha'); if (empty($conf->global->MAIN_USE_ADVANCED_PERMS)) { - $result = restrictedArea($user, 'stock', $id); + $result = restrictedArea($user, 'stock', $id, 'inventory&stock'); } else { - $result = restrictedArea($user, 'stock', $id, '', 'inventory_advance'); + $result = restrictedArea($user, 'stock', $id, 'inventory&stock', 'inventory_advance'); } // Initialize technical objects diff --git a/htdocs/product/inventory/inventory.php b/htdocs/product/inventory/inventory.php index e9ddf7ad87747..1bd941aa1fe24 100644 --- a/htdocs/product/inventory/inventory.php +++ b/htdocs/product/inventory/inventory.php @@ -59,9 +59,9 @@ $totalExpectedValuation = 0; $totalRealValuation = 0; if (empty($conf->global->MAIN_USE_ADVANCED_PERMS)) { - $result = restrictedArea($user, 'stock', $id); + $result = restrictedArea($user, 'stock', $id, 'inventory&stock'); } else { - $result = restrictedArea($user, 'stock', $id, '', 'inventory_advance'); + $result = restrictedArea($user, 'stock', $id, 'inventory&stock', 'inventory_advance'); } // Initialize technical objects diff --git a/htdocs/product/stock/card.php b/htdocs/product/stock/card.php index 5baf343556eeb..3736b0296f7e2 100644 --- a/htdocs/product/stock/card.php +++ b/htdocs/product/stock/card.php @@ -70,8 +70,7 @@ $backtopage = GETPOST('backtopage', 'alpha'); // Security check -//$result=restrictedArea($user,'stock', $id, 'entrepot&stock'); -$result = restrictedArea($user, 'stock'); +$result=restrictedArea($user,'stock', $id, 'entrepot&stock'); // Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context $hookmanager->initHooks(array('warehousecard', 'globalcard')); From 3608e9b1021fce474ac40e9e43017d1c68530bc5 Mon Sep 17 00:00:00 2001 From: atm-florian Date: Wed, 26 Nov 2025 14:44:38 +0100 Subject: [PATCH 012/172] SEC: 7ed0af2a138a34e7c7005b95c85ffc791976a6cf From 4585facfcc95470757b99a080a21b560d2f40538 Mon Sep 17 00:00:00 2001 From: ATM-Lucas Date: Wed, 26 Nov 2025 16:47:32 +0100 Subject: [PATCH 013/172] Missing AND on fields filter on evaluation class --- htdocs/hrm/class/evaluation.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/hrm/class/evaluation.class.php b/htdocs/hrm/class/evaluation.class.php index 51b0c78ed3a04..2969446f26d52 100644 --- a/htdocs/hrm/class/evaluation.class.php +++ b/htdocs/hrm/class/evaluation.class.php @@ -225,7 +225,7 @@ public function __construct(DoliDB $db) } if (!$user->hasRight('hrm', 'evaluation', 'readall')) { - $this->fields['fk_user']['type'] .= ':t.rowid:IN:'.$this->db->sanitize(implode(",", $user->getAllChildIds(1))); + $this->fields['fk_user']['type'] .= ' AND (t.rowid:IN:'.$this->db->sanitize(implode(",", $user->getAllChildIds(1))) .')'; } $this->date_eval = dol_now(); From 9e44c0a5c9d57a8489a055a07c8091bb2bdb8f48 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 26 Nov 2025 22:11:09 +0100 Subject: [PATCH 014/172] Fix CI --- htdocs/core/class/cleadstatus.class.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/htdocs/core/class/cleadstatus.class.php b/htdocs/core/class/cleadstatus.class.php index 7f6c0b9d0da46..37ab913fa5f29 100644 --- a/htdocs/core/class/cleadstatus.class.php +++ b/htdocs/core/class/cleadstatus.class.php @@ -57,6 +57,9 @@ class CLeadStatus extends CommonDict */ public $percent; + /** + * @var array|string,position:int,notnull?:int,visible:int<-5,5>|string,alwayseditable?:int<0,1>,noteditable?:int<0,1>,default?:string,index?:int,foreignkey?:string,searchall?:int<0,1>,isameasure?:int<0,1>,css?:string,csslist?:string,help?:string,showoncombobox?:int<0,4>,disabled?:int<0,1>,arrayofkeyval?:array,autofocusoncreate?:int<0,1>,comment?:string,copytoclipboard?:int<1,2>,validate?:int<0,1>,showonheader?:int<0,1>}> + */ public $fields = array( 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'position' => 1, 'notnull' => 1, 'visible' => 0, 'noteditable' => 1, 'index' => 1, 'css' => 'left', 'comment' => "Id"), 'label' => array('type' => 'varchar(128)', 'label' => 'Label', 'enabled' => 1, 'position' => 20, 'notnull' => 1, 'visible' => 1, 'index' => 1, 'searchall' => 1, 'showoncombobox' => 1, 'comment' => "Label of status"), From 3416c6ccc03053566be5e67868de4f676123efde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9?= Date: Thu, 27 Nov 2025 10:00:43 +0100 Subject: [PATCH 015/172] FIX: getDolGlobalInt --- htdocs/takepos/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/takepos/index.php b/htdocs/takepos/index.php index cf999b7ecc741..bafe040f50c1a 100644 --- a/htdocs/takepos/index.php +++ b/htdocs/takepos/index.php @@ -94,7 +94,7 @@ $MAXPRODUCT = (empty($conf->global->TAKEPOS_NB_MAXPRODUCT) ? $maxproductbydefaultforthisdevice : $conf->global->TAKEPOS_NB_MAXPRODUCT); $term = empty($_SESSION['takeposterminal']) ? 1 : $_SESSION['takeposterminal']; -$socid = getDolGlobalString('CASHDESK_ID_THIRDPARTY' . $term); +$socid = getDolGlobalInt('CASHDESK_ID_THIRDPARTY' . $term); /* $constforcompanyid = 'CASHDESK_ID_THIRDPARTY'.$_SESSION["takeposterminal"]; $soc = new Societe($db); From b2f9de74898e42917cc70232a5beb6124c4a861a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alo=C3=AFs=20Micard?= Date: Fri, 28 Nov 2025 17:27:32 +0100 Subject: [PATCH 016/172] Add new workflow to trigger Docker build --- .github/workflows/ci-on-release.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/ci-on-release.yml diff --git a/.github/workflows/ci-on-release.yml b/.github/workflows/ci-on-release.yml new file mode 100644 index 0000000000000..7c3b0d716e370 --- /dev/null +++ b/.github/workflows/ci-on-release.yml @@ -0,0 +1,22 @@ +name: "CI-RELEASE" + +on: + release: + types: [published] + +jobs: + trigger-docker: + steps: + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.RELEASE_DOCKER_ID }} + private-key: ${{ secrets.RELEASE_DOCKER_SECRET }} + + - uses: peter-evans/repository-dispatch@v4 + with: + token: ${{ steps.generate-token.outputs.token }} + repository: Dolibarr/dolibarr-docker + event-type: new-release + client-payload: '{"version": "${{ github.event.release.tag_name }}"}' From 5c6b4f62c8121ca410d08d148426896daeb59bb1 Mon Sep 17 00:00:00 2001 From: marc Date: Fri, 28 Nov 2025 18:25:00 +0100 Subject: [PATCH 017/172] FIX #35061 Signed-off-by: marc --- htdocs/core/lib/functions.lib.php | 36 ++++--------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 3b9979dd9fccc..5a79377c5730f 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -3943,46 +3943,18 @@ function dol_print_socialnetworks($value, $cid, $socid, $type, $dictsocialnetwor $htmllink .= ($link ? ' '.$link : ''); } } else { - $networkconstname = 'MAIN_INFO_SOCIETE_'.strtoupper($type).'_URL'; - if (getDolGlobalString($networkconstname)) { - $link = str_replace('{socialid}', $value, getDolGlobalString($networkconstname)); - if (preg_match('/^https?:\/\//i', $link)) { - $htmllink .= ''.dol_escape_htmltag($value).''; - } else { - $htmllink .= ''.dol_escape_htmltag($value).''; - } - } elseif (!empty($dictsocialnetworks[$type]['url'])) { - $tmpvirginurl = preg_replace('/\/?{socialid}/', '', $dictsocialnetworks[$type]['url']); - if ($tmpvirginurl) { - $value = preg_replace('/^www\.'.preg_quote($tmpvirginurl, '/').'\/?/', '', $value); - $value = preg_replace('/^'.preg_quote($tmpvirginurl, '/').'\/?/', '', $value); - - $tmpvirginurl3 = preg_replace('/^https:\/\//i', 'https://www.', $tmpvirginurl); - if ($tmpvirginurl3) { - $value = preg_replace('/^www\.'.preg_quote($tmpvirginurl3, '/').'\/?/', '', $value); - $value = preg_replace('/^'.preg_quote($tmpvirginurl3, '/').'\/?/', '', $value); - } - - $tmpvirginurl2 = preg_replace('/^https?:\/\//i', '', $tmpvirginurl); - if ($tmpvirginurl2) { - $value = preg_replace('/^www\.'.preg_quote($tmpvirginurl2, '/').'\/?/', '', $value); - $value = preg_replace('/^'.preg_quote($tmpvirginurl2, '/').'\/?/', '', $value); - } - } + if (!empty($dictsocialnetworks[$type]['url'])) { $link = str_replace('{socialid}', $value, $dictsocialnetworks[$type]['url']); - if (preg_match('/^https?:\/\//i', $link)) { - $htmllink .= ''.dol_escape_htmltag($value).''; + if (preg_match('/^https?:\/\//i', $value)) { + $htmllink .= ''.dol_escape_htmltag($value).''; } else { - $htmllink .= ''.dol_escape_htmltag($value).''; + $htmllink .= ''.dol_escape_htmltag($value).''; } } else { $htmllink .= dol_escape_htmltag($value); } } $htmllink .= ''; - } else { - $langs->load("errors"); - $htmllink .= img_warning($langs->trans("ErrorBadSocialNetworkValue", $value)); } return $htmllink; } From d16f8b68db7214611de2af081dcf47ebd8433fe4 Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Mon, 1 Dec 2025 11:14:16 +0100 Subject: [PATCH 018/172] FIX not remove value of others extra-fields on update extras action --- htdocs/core/actions_addupdatedelete.inc.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/htdocs/core/actions_addupdatedelete.inc.php b/htdocs/core/actions_addupdatedelete.inc.php index 25458a612e50d..63029dbdb83c9 100644 --- a/htdocs/core/actions_addupdatedelete.inc.php +++ b/htdocs/core/actions_addupdatedelete.inc.php @@ -351,9 +351,11 @@ if ($action == "update_extras" && GETPOST('id', 'int') > 0 && !empty($permissiontoadd)) { $object->fetch(GETPOST('id', 'int')); + $attribute = GETPOST('attribute', 'aZ09'); + $error = 0; - $ret = $extrafields->setOptionalsFromPost(null, $object, '@GETPOSTISSET'); + $ret = $extrafields->setOptionalsFromPost(null, $object, $attribute); if ($ret < 0) { $error++; setEventMessages($extrafields->error, $object->errors, 'errors'); From 0e1c580f114f95acee03e52232ba1ee9203722bd Mon Sep 17 00:00:00 2001 From: atm-florian Date: Mon, 1 Dec 2025 13:47:50 +0100 Subject: [PATCH 019/172] FIX 18.0: GETPOSTDATE and buildParamDate assumed HTTP param names 'minute' and 'second' instead of 'min' and 'sec' --- htdocs/core/lib/functions.lib.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 3de8219199a66..2ee32dfae530e 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -12996,8 +12996,8 @@ function GETPOSTDATE($prefix, $hourTime = '', $gm = 'auto') { if ($hourTime === 'getpost') { $hour = GETPOSTINT($prefix . 'hour'); - $minute = GETPOSTINT($prefix . 'minute'); - $second = GETPOSTINT($prefix . 'second'); + $minute = GETPOSTINT($prefix . 'min'); + $second = GETPOSTINT($prefix . 'sec'); } elseif (preg_match('/^(\d\d):(\d\d):(\d\d)$/', $hourTime, $m)) { $hour = intval($m[1]); $minute = intval($m[2]); @@ -13034,8 +13034,8 @@ function buildParamDate($prefix, $timestamp = null, $hourTime = '', $gm = 'auto' if ($hourTime === 'getpost' || ($timestamp !== null && dol_print_date($timestamp, '%H:%M:%S') !== '00:00:00')) { $TParam = array_merge($TParam, array( $prefix . 'hour' => intval(dol_print_date($timestamp, '%H')), - $prefix . 'minute' => intval(dol_print_date($timestamp, '%M')), - $prefix . 'second' => intval(dol_print_date($timestamp, '%S')) + $prefix . 'min' => intval(dol_print_date($timestamp, '%M')), + $prefix . 'sec' => intval(dol_print_date($timestamp, '%S')) )); } From dad4bbddd24e14b20d8e5a9470445fb3d7b7cbb9 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:00:38 +0100 Subject: [PATCH 020/172] Report fix on 21.0 Fix an issue that have been fixed by Charlene Benke on v22 --- htdocs/fichinter/card.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/htdocs/fichinter/card.php b/htdocs/fichinter/card.php index 5375c3a1831ad..e4dca1482d8a8 100644 --- a/htdocs/fichinter/card.php +++ b/htdocs/fichinter/card.php @@ -5,7 +5,7 @@ * Copyright (C) 2011-2020 Juanjo Menent * Copyright (C) 2013 Florian Henry * Copyright (C) 2014-2018 Ferran Marcet - * Copyright (C) 2014-2022 Charlene Benke + * Copyright (C) 2014-2025 Charlene Benke * Copyright (C) 2015-2016 Abbes Bahfir * Copyright (C) 2018-2022 Philippe Grand * Copyright (C) 2020-2024 Frédéric France @@ -13,6 +13,7 @@ * Copyright (C) 2023-2024 William Mead * Copyright (C) 2024 MDW * Copyright (C) 2024 Alexandre Spangaro + * Copyright (C) 2024 Pierre Ardoin * * 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 @@ -1224,6 +1225,7 @@ $formquestion[] = [ 'type' => 'select', 'name' => 'signed_status', + 'select_show_empty' => 0, 'label' => ''.$langs->trans('SignStatus').'', 'values' => $object->getSignedStatusLocalisedArray() ]; From 1c94462405bea01b5fbeaa2bd7e9c9ba648ff02d Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:03:40 +0100 Subject: [PATCH 021/172] Update copyright year for Pierre Ardoin --- htdocs/fichinter/card.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/fichinter/card.php b/htdocs/fichinter/card.php index e4dca1482d8a8..ae0e59426b281 100644 --- a/htdocs/fichinter/card.php +++ b/htdocs/fichinter/card.php @@ -13,7 +13,7 @@ * Copyright (C) 2023-2024 William Mead * Copyright (C) 2024 MDW * Copyright (C) 2024 Alexandre Spangaro - * Copyright (C) 2024 Pierre Ardoin + * Copyright (C) 2025 Pierre Ardoin * * 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 From a1476fd2217d95cd5a546635222d5db10059ab64 Mon Sep 17 00:00:00 2001 From: atm-florian Date: Wed, 3 Dec 2025 17:11:14 +0100 Subject: [PATCH 022/172] SEC: permissions not checked on other tabs of HRM evaluation card --- htdocs/hrm/evaluation_agenda.php | 5 +++-- htdocs/hrm/evaluation_contact.php | 6 ++---- htdocs/hrm/evaluation_document.php | 8 ++++---- htdocs/hrm/evaluation_note.php | 8 ++++---- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/htdocs/hrm/evaluation_agenda.php b/htdocs/hrm/evaluation_agenda.php index 628e93a3c23be..96598297cbac3 100644 --- a/htdocs/hrm/evaluation_agenda.php +++ b/htdocs/hrm/evaluation_agenda.php @@ -96,8 +96,9 @@ // Security check (enable the most restrictive one) //if ($user->socid > 0) accessforbidden(); //if ($user->socid > 0) $socid = $user->socid; -//$isdraft = (($object->status == $object::STATUS_DRAFT) ? 1 : 0); -//restrictedArea($user, $object->module, $object->id, $object->table_element, $object->element, 'fk_soc', 'rowid', $isdraft); +$isdraft = $object->status == Evaluation::STATUS_DRAFT ? 1 : 0; +restrictedArea($user, $object->element, $object, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); + if (!isModEnabled('hrm')) { accessforbidden(); } diff --git a/htdocs/hrm/evaluation_contact.php b/htdocs/hrm/evaluation_contact.php index ff41c15208034..3db6fcc836168 100644 --- a/htdocs/hrm/evaluation_contact.php +++ b/htdocs/hrm/evaluation_contact.php @@ -62,10 +62,8 @@ // Security check (enable the most restrictive one) //if ($user->socid > 0) accessforbidden(); //if ($user->socid > 0) $socid = $user->socid; -//$isdraft = (($object->status == $object::STATUS_DRAFT) ? 1 : 0); -//restrictedArea($user, $object->element, $object->id, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); -//if (empty($conf->hrm->enabled)) accessforbidden(); -//if (!$permissiontoread) accessforbidden(); +$isdraft = $object->status == Evaluation::STATUS_DRAFT ? 1 : 0; +restrictedArea($user, $object->element, $object, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); diff --git a/htdocs/hrm/evaluation_document.php b/htdocs/hrm/evaluation_document.php index a707913c3c375..70a35db02739a 100644 --- a/htdocs/hrm/evaluation_document.php +++ b/htdocs/hrm/evaluation_document.php @@ -85,10 +85,10 @@ $permissiontoread = $user->rights->hrm->evaluation->read; // Security check (enable the most restrictive one) -//if ($user->socid > 0) accessforbidden(); -//if ($user->socid > 0) $socid = $user->socid; -//$isdraft = (($object->status == $object::STATUS_DRAFT) ? 1 : 0); -//restrictedArea($user, $object->element, $object->id, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); + +$isdraft = $object->status == Evaluation::STATUS_DRAFT ? 1 : 0; +restrictedArea($user, $object->element, $object, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); + if (empty($conf->hrm->enabled)) accessforbidden(); if (!$permissiontoread) accessforbidden(); diff --git a/htdocs/hrm/evaluation_note.php b/htdocs/hrm/evaluation_note.php index ba0ec02d616eb..947bca9eb0326 100644 --- a/htdocs/hrm/evaluation_note.php +++ b/htdocs/hrm/evaluation_note.php @@ -66,10 +66,10 @@ // Security check (enable the most restrictive one) //if ($user->socid > 0) accessforbidden(); //if ($user->socid > 0) $socid = $user->socid; -//$isdraft = (($object->status == $object::STATUS_DRAFT) ? 1 : 0); -//restrictedArea($user, $object->element, $object->id, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); -//if (empty($conf->hrm->enabled)) accessforbidden(); -//if (!$permissiontoread) accessforbidden(); +$isdraft = (($object->status == Evaluation::STATUS_DRAFT) ? 1 : 0); +restrictedArea($user, $object->element, $object, $object->table_element, '', 'fk_soc', 'rowid', $isdraft); +if (empty($conf->hrm->enabled)) accessforbidden(); +if (!$permissiontoread) accessforbidden(); /* From 2ee07bb0494bf5a95e33c6f1cfed5bbcd2271554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9?= Date: Thu, 4 Dec 2025 17:27:57 +0100 Subject: [PATCH 023/172] FIX: Remove ") > 0) { - socid = parseInt(""); + if ((socid === undefined || socid === "") && parseInt("") > 0) { + socid = parseInt(""); } // Only show products for sale (tosell=1) @@ -449,8 +449,8 @@ function MoreProducts(moreorless) { // Get socid let socid = jQuery('#thirdpartyid').val(); - if ((socid === undefined || socid === "") && parseInt("") > 0) { - socid = parseInt(""); + if ((socid === undefined || socid === "") && parseInt("") > 0) { + socid = parseInt(""); } // Only show products for sale (tosell=1) @@ -681,8 +681,8 @@ function Search2(keyCodeForEnter, moreorless) { // Only show products for sale (tosell=1) let socid = jQuery('#thirdpartyid').val(); - if ((socid === undefined || socid === "") && parseInt("") > 0) { - socid = parseInt(""); + if ((socid === undefined || socid === "") && parseInt("") > 0) { + socid = parseInt(""); } $.getJSON('/takepos/ajax/ajax.php?action=search&token=&term=' + search_term + '&thirdpartyid=' + socid + '&search_start=' + search_start + '&search_limit=' + search_limit, function (data) { From a9d2c95fdd503b40454d1c470545f7932a28520d Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 4 Dec 2025 21:08:17 +0100 Subject: [PATCH 024/172] Fix CI --- .github/workflows/ci-on-release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci-on-release.yml b/.github/workflows/ci-on-release.yml index 7c3b0d716e370..958b8865b58a0 100644 --- a/.github/workflows/ci-on-release.yml +++ b/.github/workflows/ci-on-release.yml @@ -6,6 +6,8 @@ on: jobs: trigger-docker: + runs-on: ubuntu-latest + steps: - name: Generate a token id: generate-token From 8fe5d2758a26bde6499cdb417b97f6f1c42e6aa0 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 4 Dec 2025 21:08:17 +0100 Subject: [PATCH 025/172] Fix CI --- .github/workflows/ci-on-release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci-on-release.yml b/.github/workflows/ci-on-release.yml index 7c3b0d716e370..958b8865b58a0 100644 --- a/.github/workflows/ci-on-release.yml +++ b/.github/workflows/ci-on-release.yml @@ -6,6 +6,8 @@ on: jobs: trigger-docker: + runs-on: ubuntu-latest + steps: - name: Generate a token id: generate-token From 9ac82f488efdd9a9e3b69847951e05fa5d8910a1 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 4 Dec 2025 21:08:17 +0100 Subject: [PATCH 026/172] Fix CI --- .github/workflows/ci-on-release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci-on-release.yml b/.github/workflows/ci-on-release.yml index 7c3b0d716e370..958b8865b58a0 100644 --- a/.github/workflows/ci-on-release.yml +++ b/.github/workflows/ci-on-release.yml @@ -6,6 +6,8 @@ on: jobs: trigger-docker: + runs-on: ubuntu-latest + steps: - name: Generate a token id: generate-token From 764bb3791b521db22b6ebe30813786690aa386be Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 4 Dec 2025 21:08:17 +0100 Subject: [PATCH 027/172] Fix CI --- .github/workflows/ci-on-release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci-on-release.yml b/.github/workflows/ci-on-release.yml index 7c3b0d716e370..958b8865b58a0 100644 --- a/.github/workflows/ci-on-release.yml +++ b/.github/workflows/ci-on-release.yml @@ -6,6 +6,8 @@ on: jobs: trigger-docker: + runs-on: ubuntu-latest + steps: - name: Generate a token id: generate-token From a567cade7d876db81c6baa5fd356202d6da34b10 Mon Sep 17 00:00:00 2001 From: William Mead Date: Fri, 5 Dec 2025 08:56:14 +0100 Subject: [PATCH 028/172] Fixed shipping and availability updating. Updated contributor details. --- htdocs/comm/propal/class/propal.class.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/htdocs/comm/propal/class/propal.class.php b/htdocs/comm/propal/class/propal.class.php index 28e336247bfc3..4b3d606eee785 100644 --- a/htdocs/comm/propal/class/propal.class.php +++ b/htdocs/comm/propal/class/propal.class.php @@ -18,6 +18,7 @@ * Copyright (C) 2022 ATM Consulting * Copyright (C) 2022 OpenDSI * Copyright (C) 2022 Gauthier VERDOL + * Copyright (C) 2025 William Mead * * 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 @@ -1791,6 +1792,8 @@ public function update(User $user, $notrigger = 0) $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").","; $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").","; $sql .= " fk_input_reason=".(isset($this->demand_reason_id) ? $this->demand_reason_id : "null").","; + $sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? (int) $this->shipping_method_id : "null").","; + $sql .= " fk_availability=".(isset($this->availability_id) ? (int) $this->availability_id : "null").","; $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").","; $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").","; $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").","; From cc5a8fd15a08498992ff4a414d07fc6b4c13ca16 Mon Sep 17 00:00:00 2001 From: atm-florian Date: Fri, 5 Dec 2025 09:29:15 +0100 Subject: [PATCH 029/172] FIX: PR#36401 fixed a missing GETPOSTISSET() but the check involves a variable ($taskid) that was renamed ($tmptaskid) in 18.0 --- htdocs/projet/activity/perweek.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/projet/activity/perweek.php b/htdocs/projet/activity/perweek.php index 2fa96c6decc81..370f9d93c7568 100644 --- a/htdocs/projet/activity/perweek.php +++ b/htdocs/projet/activity/perweek.php @@ -308,7 +308,7 @@ } } - if (!$updateoftaskdone && GETPOSTISSET($taskid.'progress')) { // Check to update progress if no update were done on task. + if (!$updateoftaskdone && GETPOSTISSET($tmptaskid.'progress')) { // Check to update progress if no update were done on task. $object->fetch($tmptaskid); //var_dump($object->progress); //var_dump(GETPOST($tmptaskid . 'progress', 'int')); exit; From c6ed34ebcc36ae4295dd95e48608400a0a2d304a Mon Sep 17 00:00:00 2001 From: Eric Seigne Date: Fri, 5 Dec 2025 09:32:03 +0100 Subject: [PATCH 030/172] backport of PR36378 : Problem with deposit line on Type of operation --- .../modules/facture/doc/pdf_crabe.modules.php | 213 ++++++++++-------- .../facture/doc/pdf_sponge.modules.php | 13 ++ 2 files changed, 129 insertions(+), 97 deletions(-) diff --git a/htdocs/core/modules/facture/doc/pdf_crabe.modules.php b/htdocs/core/modules/facture/doc/pdf_crabe.modules.php index dee3bb26f92d8..587cb3cbe5770 100644 --- a/htdocs/core/modules/facture/doc/pdf_crabe.modules.php +++ b/htdocs/core/modules/facture/doc/pdf_crabe.modules.php @@ -32,11 +32,11 @@ * \brief File of class to generate customers invoices from crabe model */ -require_once DOL_DOCUMENT_ROOT.'/core/modules/facture/modules_facture.php'; -require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php'; +require_once DOL_DOCUMENT_ROOT . '/core/modules/facture/modules_facture.php'; +require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php'; +require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT . '/core/lib/pdf.lib.php'; /** @@ -44,9 +44,9 @@ */ class pdf_crabe extends ModelePDFFactures { - /** - * @var DoliDb Database handler - */ + /** + * @var DoliDb Database handler + */ public $db; /** @@ -236,7 +236,7 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede // phpcs:enable global $user, $langs, $conf, $mysoc, $hookmanager, $nblines; - dol_syslog("write_file outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null')); + dol_syslog("write_file outputlangs->defaultlang=" . (is_object($outputlangs) ? $outputlangs->defaultlang : 'null')); if (!is_object($outputlangs)) { $outputlangs = $langs; @@ -275,14 +275,14 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede $objphoto = new Product($this->db); $objphoto->fetch($object->lines[$i]->fk_product); - $pdir = get_exdir($object->lines[$i]->fk_product, 2, 0, 0, $objphoto, 'product').$object->lines[$i]->fk_product."/photos/"; - $dir = $conf->product->dir_output.'/'.$pdir; + $pdir = get_exdir($object->lines[$i]->fk_product, 2, 0, 0, $objphoto, 'product') . $object->lines[$i]->fk_product . "/photos/"; + $dir = $conf->product->dir_output . '/' . $pdir; $realpath = ''; foreach ($objphoto->liste_photos($dir, 1) as $key => $obj) { $filename = $obj['photo']; //if ($obj['photo_vignette']) $filename='thumbs/'.$obj['photo_vignette']; - $realpath = $dir.$filename; + $realpath = $dir . $filename; break; } @@ -305,11 +305,11 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede // Definition of $dir and $file if ($object->specimen) { $dir = empty($conf->facture->multidir_output[$object->entity]) ? $conf->facture->dir_output : $conf->facture->multidir_output[$object->entity]; - $file = $dir."/SPECIMEN.pdf"; + $file = $dir . "/SPECIMEN.pdf"; } else { $objectref = dol_sanitizeFileName($object->ref); - $dir = (empty($conf->facture->multidir_output[$object->entity]) ? $conf->facture->dir_output : $conf->facture->multidir_output[$object->entity])."/".$objectref; - $file = $dir."/".$objectref.".pdf"; + $dir = (empty($conf->facture->multidir_output[$object->entity]) ? $conf->facture->dir_output : $conf->facture->multidir_output[$object->entity]) . "/" . $objectref; + $file = $dir . "/" . $objectref . ".pdf"; } if (!file_exists($dir)) { if (dol_mkdir($dir) < 0) { @@ -321,11 +321,11 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede if (file_exists($dir)) { // Add pdfgeneration hook if (!is_object($hookmanager)) { - include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php'; + include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php'; $hookmanager = new HookManager($this->db); } $hookmanager->initHooks(array('pdfgeneration')); - $parameters = array('file'=>$file, 'object'=>$object, 'outputlangs'=>$outputlangs); + $parameters = array('file' => $file, 'object' => $object, 'outputlangs' => $outputlangs); global $action; $reshook = $hookmanager->executeHooks('beforePDFCreation', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks @@ -369,7 +369,7 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede if (!empty($conf->mycompany->multidir_output[$object->entity])) { $logodir = $conf->mycompany->multidir_output[$object->entity]; } - $pagecount = $pdf->setSourceFile($logodir.'/'.$conf->global->MAIN_ADD_PDF_BACKGROUND); + $pagecount = $pdf->setSourceFile($logodir . '/' . $conf->global->MAIN_ADD_PDF_BACKGROUND); $tplidx = $pdf->importPage(1); } @@ -379,9 +379,9 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede $pdf->SetTitle($outputlangs->convToOutputCharset($object->ref)); $pdf->SetSubject($outputlangs->transnoentities("PdfInvoiceTitle")); - $pdf->SetCreator("Dolibarr ".DOL_VERSION); - $pdf->SetAuthor($mysoc->name.($user->id > 0 ? ' - '.$outputlangs->convToOutputCharset($user->getFullName($outputlangs)) : '')); - $pdf->SetKeyWords($outputlangs->convToOutputCharset($object->ref)." ".$outputlangs->transnoentities("PdfInvoiceTitle")." ".$outputlangs->convToOutputCharset($object->thirdparty->name)); + $pdf->SetCreator("Dolibarr " . DOL_VERSION); + $pdf->SetAuthor($mysoc->name . ($user->id > 0 ? ' - ' . $outputlangs->convToOutputCharset($user->getFullName($outputlangs)) : '')); + $pdf->SetKeyWords($outputlangs->convToOutputCharset($object->ref) . " " . $outputlangs->transnoentities("PdfInvoiceTitle") . " " . $outputlangs->convToOutputCharset($object->thirdparty->name)); if (getDolGlobalString('MAIN_DISABLE_PDF_COMPRESSION')) { $pdf->SetCompression(false); } @@ -419,6 +419,19 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede $this->atleastonediscount++; } + // Do not take into account lines of the type “deposit.” + $is_deposit = false; + if (preg_match('/^\((.*)\)$/', $object->lines[$i]->desc, $reg)) { + if ($reg[1] == 'DEPOSIT') { + $is_deposit = true; + } + } + // If DEPOSIT, this line is completely ignored for calculations. + if ($is_deposit) { + continue; + } + + // determine category of operation if ($categoryOfOperation < 2) { $lineProductType = $object->lines[$i]->product_type; @@ -513,7 +526,7 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede $parameters = array( 'object' => $object, 'i' => $i, - 'pdf' =>& $pdf, + 'pdf' => &$pdf, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails ); @@ -781,7 +794,8 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede // Retrieve type from database for backward compatibility with old records if ((!isset($localtax1_type) || $localtax1_type == '' || !isset($localtax2_type) || $localtax2_type == '') // if tax type not defined - && (!empty($localtax1_rate) || !empty($localtax2_rate))) { // and there is local tax + && (!empty($localtax1_rate) || !empty($localtax2_rate)) + ) { // and there is local tax $localtaxtmp_array = getLocalTaxesFromRate($vatrate, 0, $object->thirdparty, $mysoc); $localtax1_type = isset($localtaxtmp_array[0]) ? $localtaxtmp_array[0] : ''; $localtax2_type = isset($localtaxtmp_array[2]) ? $localtaxtmp_array[2] : ''; @@ -813,10 +827,10 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede } $this->tva[$vatrate] += $tvaligne; // ->tva is abandonned, we use now ->tva_array that is more complete $vatcode = $object->lines[$i]->vat_src_code; - if (empty($this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'])) { - $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'] = 0; + if (empty($this->tva_array[$vatrate . ($vatcode ? ' (' . $vatcode . ')' : '')]['amount'])) { + $this->tva_array[$vatrate . ($vatcode ? ' (' . $vatcode . ')' : '')]['amount'] = 0; } - $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')] = array('vatrate'=>$vatrate, 'vatcode'=>$vatcode, 'amount'=> $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'] + $tvaligne); + $this->tva_array[$vatrate . ($vatcode ? ' (' . $vatcode . ')' : '')] = array('vatrate' => $vatrate, 'vatcode' => $vatcode, 'amount' => $this->tva_array[$vatrate . ($vatcode ? ' (' . $vatcode . ')' : '')]['amount'] + $tvaligne); if ($posYAfterImage > $posYAfterDescription) { $nexY = $posYAfterImage; @@ -825,10 +839,10 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede // Add line if (!empty($conf->global->MAIN_PDF_DASH_BETWEEN_LINES) && $i < ($nblines - 1)) { $pdf->setPage($pageposafter); - $pdf->SetLineStyle(array('dash'=>'1,1', 'color'=>array(80, 80, 80))); + $pdf->SetLineStyle(array('dash' => '1,1', 'color' => array(80, 80, 80))); //$pdf->SetDrawColor(190,190,200); $pdf->line($this->marge_gauche, $nexY + 1, $this->page_largeur - $this->marge_droite, $nexY + 1); - $pdf->SetLineStyle(array('dash'=>0)); + $pdf->SetLineStyle(array('dash' => 0)); } $nexY += 2; // Add space between lines @@ -881,7 +895,7 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede $this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforinfotot - $heightforfreetext - $heightforfooter, 0, $outputlangs, 1, 0, $object->multicurrency_code); $bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforfooter + 1; } - dol_syslog("bottomlasttab=".$bottomlasttab." this->page_hauteur=".$this->page_hauteur." heightforinfotot=".$heightforinfotot." heightforfreetext=".$heightforfreetext." heightforfooter=".$heightforfooter); + dol_syslog("bottomlasttab=" . $bottomlasttab . " this->page_hauteur=" . $this->page_hauteur . " heightforinfotot=" . $heightforinfotot . " heightforfreetext=" . $heightforfreetext . " heightforfooter=" . $heightforfooter); // Display info area $posy = $this->_tableau_info($pdf, $object, $bottomlasttab, $outputlangs, $outputlangsbis); @@ -913,7 +927,7 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede // Add pdfgeneration hook $hookmanager->initHooks(array('pdfgeneration')); - $parameters = array('file'=>$file, 'object'=>$object, 'outputlangs'=>$outputlangs); + $parameters = array('file' => $file, 'object' => $object, 'outputlangs' => $outputlangs); global $action; $reshook = $hookmanager->executeHooks('afterPDFCreation', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks if ($reshook < 0) { @@ -923,7 +937,7 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede dolChmod($file); - $this->result = array('fullpath'=>$file); + $this->result = array('fullpath' => $file); return 1; // No error } else { @@ -981,8 +995,8 @@ protected function _tableau_versements(&$pdf, $object, $posy, $outputlangs, $hei $sql = "SELECT re.rowid, re.amount_ht, re.multicurrency_amount_ht, re.amount_tva, re.multicurrency_amount_tva, re.amount_ttc, re.multicurrency_amount_ttc,"; $sql .= " re.description, re.fk_facture_source,"; $sql .= " f.type, f.datef"; - $sql .= " FROM ".MAIN_DB_PREFIX."societe_remise_except as re, ".MAIN_DB_PREFIX."facture as f"; - $sql .= " WHERE re.fk_facture_source = f.rowid AND re.fk_facture = ".((int) $object->id); + $sql .= " FROM " . MAIN_DB_PREFIX . "societe_remise_except as re, " . MAIN_DB_PREFIX . "facture as f"; + $sql .= " WHERE re.fk_facture_source = f.rowid AND re.fk_facture = " . ((int) $object->id); $resql = $this->db->query($sql); if ($resql) { $num = $this->db->num_rows($resql); @@ -1041,9 +1055,9 @@ protected function _tableau_versements(&$pdf, $object, $posy, $outputlangs, $hei // TODO Call getListOfPaymentsgetListOfPayments instead of hard coded sql $sql = "SELECT p.datep as date, p.fk_paiement, p.num_paiement as num, pf.amount as amount, pf.multicurrency_amount,"; $sql .= " cp.code"; - $sql .= " FROM ".MAIN_DB_PREFIX."paiement_facture as pf, ".MAIN_DB_PREFIX."paiement as p"; - $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_paiement as cp ON p.fk_paiement = cp.id"; - $sql .= " WHERE pf.fk_paiement = p.rowid AND pf.fk_facture = ".((int) $object->id); + $sql .= " FROM " . MAIN_DB_PREFIX . "paiement_facture as pf, " . MAIN_DB_PREFIX . "paiement as p"; + $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_paiement as cp ON p.fk_paiement = cp.id"; + $sql .= " WHERE pf.fk_paiement = p.rowid AND pf.fk_facture = " . ((int) $object->id); //$sql.= " WHERE pf.fk_paiement = p.rowid AND pf.fk_facture = 1"; $sql .= " ORDER BY p.datep"; @@ -1075,7 +1089,7 @@ protected function _tableau_versements(&$pdf, $object, $posy, $outputlangs, $hei $pdf->SetXY($tab3_posx + 21, $tab3_top + $y); $pdf->MultiCell(20, 3, price($sign * ((isModEnabled("multicurrency") && $object->multicurrency_tx != 1) ? $row->multicurrency_amount : $row->amount), 0, $outputlangs), 0, 'L', 0); $pdf->SetXY($tab3_posx + 40, $tab3_top + $y); - $oper = $outputlangs->transnoentitiesnoconv("PaymentTypeShort".$row->code); + $oper = $outputlangs->transnoentitiesnoconv("PaymentTypeShort" . $row->code); $pdf->MultiCell(20, 3, $oper, 0, 'L', 0); $pdf->SetXY($tab3_posx + 58, $tab3_top + $y); @@ -1179,12 +1193,12 @@ protected function _tableau_info(&$pdf, $object, $posy, $outputlangs, $outputlan if ($object->type != 2 && ($object->cond_reglement_code || $object->cond_reglement)) { $pdf->SetFont('', 'B', $default_font_size - 2); $pdf->SetXY($this->marge_gauche, $posy); - $titre = $outputlangs->transnoentities("PaymentConditions").':'; + $titre = $outputlangs->transnoentities("PaymentConditions") . ':'; $pdf->MultiCell(43, 4, $titre, 0, 'L'); $pdf->SetFont('', '', $default_font_size - 2); $pdf->SetXY($posxval, $posy); - $lib_condition_paiement = $outputlangs->transnoentities("PaymentCondition".$object->cond_reglement_code) != ('PaymentCondition'.$object->cond_reglement_code) ? $outputlangs->transnoentities("PaymentCondition".$object->cond_reglement_code) : $outputlangs->convToOutputCharset($object->cond_reglement_doc ? $object->cond_reglement_doc : $object->cond_reglement_label); + $lib_condition_paiement = $outputlangs->transnoentities("PaymentCondition" . $object->cond_reglement_code) != ('PaymentCondition' . $object->cond_reglement_code) ? $outputlangs->transnoentities("PaymentCondition" . $object->cond_reglement_code) : $outputlangs->convToOutputCharset($object->cond_reglement_doc ? $object->cond_reglement_doc : $object->cond_reglement_label); $lib_condition_paiement = str_replace('\n', "\n", $lib_condition_paiement); $pdf->MultiCell(67, 4, $lib_condition_paiement, 0, 'L'); @@ -1195,7 +1209,7 @@ protected function _tableau_info(&$pdf, $object, $posy, $outputlangs, $outputlan if (getDolGlobalInt('INVOICE_CATEGORY_OF_OPERATION') == 2 && $this->categoryOfOperation >= 0) { $pdf->SetFont('', 'B', $default_font_size - 2); $pdf->SetXY($this->marge_gauche, $posy); - $categoryOfOperationTitle = $outputlangs->transnoentities("MentionCategoryOfOperations").' : '; + $categoryOfOperationTitle = $outputlangs->transnoentities("MentionCategoryOfOperations") . ' : '; $pdf->MultiCell($posxval - $this->marge_gauche, 4, $categoryOfOperationTitle, 0, 'L'); $pdf->SetFont('', '', $default_font_size - 2); @@ -1208,12 +1222,15 @@ protected function _tableau_info(&$pdf, $object, $posy, $outputlangs, $outputlan if ($object->type != 2) { // Check a payment mode is defined - if (empty($object->mode_reglement_code) - && !getDolGlobalInt('FACTURE_CHQ_NUMBER') - && !getDolGlobalInt('FACTURE_RIB_NUMBER')) { + if ( + empty($object->mode_reglement_code) + && !getDolGlobalInt('FACTURE_CHQ_NUMBER') + && !getDolGlobalInt('FACTURE_RIB_NUMBER') + ) { $this->error = $outputlangs->transnoentities("ErrorNoPaiementModeConfigured"); } elseif (($object->mode_reglement_code == 'CHQ' && !getDolGlobalInt('FACTURE_CHQ_NUMBER') && empty($object->fk_account) && empty($object->fk_bank)) - || ($object->mode_reglement_code == 'VIR' && !getDolGlobalInt('FACTURE_RIB_NUMBER') && empty($object->fk_account) && empty($object->fk_bank))) { + || ($object->mode_reglement_code == 'VIR' && !getDolGlobalInt('FACTURE_RIB_NUMBER') && empty($object->fk_account) && empty($object->fk_bank)) + ) { // Avoid having any valid PDF with setup that is not complete $outputlangs->load("errors"); @@ -1228,24 +1245,26 @@ protected function _tableau_info(&$pdf, $object, $posy, $outputlangs, $outputlan } // Show payment mode - if (!empty($object->mode_reglement_code) - && $object->mode_reglement_code != 'CHQ' - && $object->mode_reglement_code != 'VIR') { + if ( + !empty($object->mode_reglement_code) + && $object->mode_reglement_code != 'CHQ' + && $object->mode_reglement_code != 'VIR' + ) { $pdf->SetFont('', 'B', $default_font_size - 2); $pdf->SetXY($this->marge_gauche, $posy); - $titre = $outputlangs->transnoentities("PaymentMode").':'; + $titre = $outputlangs->transnoentities("PaymentMode") . ':'; $pdf->MultiCell(80, 5, $titre, 0, 'L'); $pdf->SetFont('', '', $default_font_size - 2); $pdf->SetXY($posxval, $posy); - $lib_mode_reg = $outputlangs->transnoentities("PaymentType".$object->mode_reglement_code) != ('PaymentType'.$object->mode_reglement_code) ? $outputlangs->transnoentities("PaymentType".$object->mode_reglement_code) : $outputlangs->convToOutputCharset($object->mode_reglement); + $lib_mode_reg = $outputlangs->transnoentities("PaymentType" . $object->mode_reglement_code) != ('PaymentType' . $object->mode_reglement_code) ? $outputlangs->transnoentities("PaymentType" . $object->mode_reglement_code) : $outputlangs->convToOutputCharset($object->mode_reglement); //#21654: add account number used for the debit if ($object->mode_reglement_code == "PRE") { - require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php'; + require_once DOL_DOCUMENT_ROOT . '/societe/class/companybankaccount.class.php'; $bac = new CompanyBankAccount($this->db); $bac->fetch(0, $object->thirdparty->id); - $iban= $bac->iban.(($bac->iban && $bac->bic) ? ' / ' : '').$bac->bic; - $lib_mode_reg .= ' '.$outputlangs->trans("PaymentTypePREdetails", dol_trunc($iban, 6, 'right', 'UTF-8', 1)); + $iban = $bac->iban . (($bac->iban && $bac->bic) ? ' / ' : '') . $bac->bic; + $lib_mode_reg .= ' ' . $outputlangs->trans("PaymentTypePREdetails", dol_trunc($iban, 6, 'right', 'UTF-8', 1)); } $pdf->MultiCell(80, 5, $lib_mode_reg, 0, 'L'); @@ -1280,13 +1299,13 @@ protected function _tableau_info(&$pdf, $object, $posy, $outputlangs, $outputlan } if ($object->statut != Facture::STATUS_DRAFT && $useonlinepayment) { - require_once DOL_DOCUMENT_ROOT.'/core/lib/payments.lib.php'; + require_once DOL_DOCUMENT_ROOT . '/core/lib/payments.lib.php'; global $langs; $langs->loadLangs(array('payment', 'paybox', 'stripe')); $servicename = $langs->transnoentities('Online'); $paiement_url = getOnlinePaymentUrl('', 'invoice', $object->ref, '', '', ''); - $linktopay = $langs->trans("ToOfferALinkForOnlinePayment", $servicename).' '.$outputlangs->transnoentities("ClickHere").''; + $linktopay = $langs->trans("ToOfferALinkForOnlinePayment", $servicename) . ' ' . $outputlangs->transnoentities("ClickHere") . ''; $pdf->SetXY($this->marge_gauche, $posy); $pdf->writeHTMLCell(80, 5, '', '', dol_htmlentitiesbr($linktopay), 0, 1); @@ -1409,7 +1428,7 @@ protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs // Total HT $pdf->SetFillColor(255, 255, 255); $pdf->SetXY($col1x, $tab2_top); - $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities(empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT) ? "TotalHT" : "Total").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities(empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT) ? "TotalHT" : "Total") : ''), 0, 'L', 1); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities(empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT) ? "TotalHT" : "Total") . (is_object($outputlangsbis) ? ' / ' . $outputlangsbis->transnoentities(empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT) ? "TotalHT" : "Total") : ''), 0, 'L', 1); $total_ht = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? $object->multicurrency_total_ht : $object->total_ht); $pdf->SetXY($col2x, $tab2_top); @@ -1444,12 +1463,12 @@ protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs $tvacompl = ''; if (preg_match('/\*/', $tvakey)) { $tvakey = str_replace('*', '', $tvakey); - $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + $tvacompl = " (" . $outputlangs->transnoentities("NonPercuRecuperable") . ")"; } - $totalvat = $outputlangs->transcountrynoentities("TotalLT1", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalLT1", $mysoc->country_code) : ''); + $totalvat = $outputlangs->transcountrynoentities("TotalLT1", $mysoc->country_code) . (is_object($outputlangsbis) ? ' / ' . $outputlangsbis->transcountrynoentities("TotalLT1", $mysoc->country_code) : ''); $totalvat .= ' '; - $totalvat .= vatrate(abs($tvakey), 1).$tvacompl; + $totalvat .= vatrate(abs($tvakey), 1) . $tvacompl; $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); @@ -1478,11 +1497,11 @@ protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs $tvacompl = ''; if (preg_match('/\*/', $tvakey)) { $tvakey = str_replace('*', '', $tvakey); - $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + $tvacompl = " (" . $outputlangs->transnoentities("NonPercuRecuperable") . ")"; } - $totalvat = $outputlangs->transcountrynoentities("TotalLT2", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalLT2", $mysoc->country_code) : ''); + $totalvat = $outputlangs->transcountrynoentities("TotalLT2", $mysoc->country_code) . (is_object($outputlangsbis) ? ' / ' . $outputlangsbis->transcountrynoentities("TotalLT2", $mysoc->country_code) : ''); $totalvat .= ' '; - $totalvat .= vatrate(abs($tvakey), 1).$tvacompl; + $totalvat .= vatrate(abs($tvakey), 1) . $tvacompl; $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); @@ -1506,16 +1525,16 @@ protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs $tvacompl = ''; if (preg_match('/\*/', $tvakey)) { $tvakey = str_replace('*', '', $tvakey); - $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + $tvacompl = " (" . $outputlangs->transnoentities("NonPercuRecuperable") . ")"; } - $totalvat = $outputlangs->transcountrynoentities("TotalVAT", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalVAT", $mysoc->country_code) : ''); + $totalvat = $outputlangs->transcountrynoentities("TotalVAT", $mysoc->country_code) . (is_object($outputlangsbis) ? ' / ' . $outputlangsbis->transcountrynoentities("TotalVAT", $mysoc->country_code) : ''); $totalvat .= ' '; if (getDolGlobalString('PDF_VAT_LABEL_IS_CODE_OR_RATE') == 'rateonly') { - $totalvat .= vatrate($tvaval['vatrate'], 1).$tvacompl; + $totalvat .= vatrate($tvaval['vatrate'], 1) . $tvacompl; } elseif (getDolGlobalString('PDF_VAT_LABEL_IS_CODE_OR_RATE') == 'codeonly') { - $totalvat .= $tvaval['vatcode'].$tvacompl; + $totalvat .= $tvaval['vatcode'] . $tvacompl; } else { - $totalvat .= vatrate($tvaval['vatrate'], 1).($tvaval['vatcode'] ? ' ('.$tvaval['vatcode'].')' : '').$tvacompl; + $totalvat .= vatrate($tvaval['vatrate'], 1) . ($tvaval['vatcode'] ? ' (' . $tvaval['vatcode'] . ')' : '') . $tvacompl; } $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); @@ -1542,10 +1561,10 @@ protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs $tvacompl = ''; if (preg_match('/\*/', $tvakey)) { $tvakey = str_replace('*', '', $tvakey); - $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + $tvacompl = " (" . $outputlangs->transnoentities("NonPercuRecuperable") . ")"; } - $totalvat = $outputlangs->transcountrynoentities("TotalLT1", $mysoc->country_code).' '; - $totalvat .= vatrate(abs($tvakey), 1).$tvacompl; + $totalvat = $outputlangs->transcountrynoentities("TotalLT1", $mysoc->country_code) . ' '; + $totalvat .= vatrate(abs($tvakey), 1) . $tvacompl; $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); @@ -1574,11 +1593,11 @@ protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs $tvacompl = ''; if (preg_match('/\*/', $tvakey)) { $tvakey = str_replace('*', '', $tvakey); - $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + $tvacompl = " (" . $outputlangs->transnoentities("NonPercuRecuperable") . ")"; } - $totalvat = $outputlangs->transcountrynoentities("TotalLT2", $mysoc->country_code).' '; + $totalvat = $outputlangs->transcountrynoentities("TotalLT2", $mysoc->country_code) . ' '; - $totalvat .= vatrate(abs($tvakey), 1).$tvacompl; + $totalvat .= vatrate(abs($tvakey), 1) . $tvacompl; $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); @@ -1629,8 +1648,8 @@ protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs $index++; $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); - $retainedWarrantyToPayOn = $outputlangs->transnoentities("RetainedWarranty").' ('.$object->retained_warranty.'%)'; - $retainedWarrantyToPayOn .= !empty($object->retained_warranty_date_limit) ? ' '.$outputlangs->transnoentities("toPayOn", dol_print_date($object->retained_warranty_date_limit, 'day')) : ''; + $retainedWarrantyToPayOn = $outputlangs->transnoentities("RetainedWarranty") . ' (' . $object->retained_warranty . '%)'; + $retainedWarrantyToPayOn .= !empty($object->retained_warranty_date_limit) ? ' ' . $outputlangs->transnoentities("toPayOn", dol_print_date($object->retained_warranty_date_limit, 'day')) : ''; $pdf->MultiCell($col2x - $col1x, $tab2_hl, $retainedWarrantyToPayOn, $useborder, 'L', 1); $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); @@ -1734,7 +1753,7 @@ protected function _tableau(&$pdf, $tab_top, $tab_height, $nexY, $outputlangs, $ $pdf->MultiCell(($pdf->GetStringWidth($categoryOfOperations)) + 4, 2, $categoryOfOperations); } - $titre = $outputlangs->transnoentities("AmountInCurrency", $outputlangs->transnoentitiesnoconv("Currency".$currency)); + $titre = $outputlangs->transnoentities("AmountInCurrency", $outputlangs->transnoentitiesnoconv("Currency" . $currency)); $pdf->SetXY($this->page_largeur - $this->marge_droite - ($pdf->GetStringWidth($titre) + 3), $tab_top - 4); $pdf->MultiCell(($pdf->GetStringWidth($titre) + 3), 2, $titre); @@ -1859,9 +1878,9 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $logodir = $conf->mycompany->multidir_output[$object->entity]; } if (!getDolGlobalInt('MAIN_PDF_USE_LARGE_LOGO')) { - $logo = $logodir.'/logos/thumbs/'.$this->emetteur->logo_small; + $logo = $logodir . '/logos/thumbs/' . $this->emetteur->logo_small; } else { - $logo = $logodir.'/logos/'.$this->emetteur->logo; + $logo = $logodir . '/logos/' . $this->emetteur->logo; } if (is_readable($logo)) { $height = pdf_getHeightForLogo($logo); @@ -1915,10 +1934,10 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $title .= $outputlangsbis->transnoentities("InvoiceProForma"); } } - $title .= ' '.$outputlangs->convToOutputCharset($object->ref); + $title .= ' ' . $outputlangs->convToOutputCharset($object->ref); if ($object->statut == $object::STATUS_DRAFT) { $pdf->SetTextColor(128, 0, 0); - $title .= ' - '.$outputlangs->transnoentities("NotValidated"); + $title .= ' - ' . $outputlangs->transnoentities("NotValidated"); } $pdf->MultiCell($w, 3, $title, '', 'R'); @@ -1943,7 +1962,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy += 4; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefCustomer")." : ".$outputlangs->convToOutputCharset($object->ref_customer), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefCustomer") . " : " . $outputlangs->convToOutputCharset($object->ref_customer), '', 'R'); } if (!empty($conf->global->PDF_SHOW_PROJECT_TITLE)) { @@ -1952,7 +1971,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy += 3; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("Project")." : ".(empty($object->project->title) ? '' : $object->project->title), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("Project") . " : " . (empty($object->project->title) ? '' : $object->project->title), '', 'R'); } } @@ -1963,7 +1982,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy += 3; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefProject")." : ".(empty($object->project->ref) ? '' : $object->project->ref), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefProject") . " : " . (empty($object->project->ref) ? '' : $object->project->ref), '', 'R'); } } @@ -1975,7 +1994,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy += 3; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ReplacementByInvoice").' : '.$outputlangs->convToOutputCharset($objectreplacing->ref), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ReplacementByInvoice") . ' : ' . $outputlangs->convToOutputCharset($objectreplacing->ref), '', 'R'); } if ($object->type == 1) { $objectreplaced = new Facture($this->db); @@ -1984,7 +2003,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy += 4; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ReplacementInvoice").' : '.$outputlangs->convToOutputCharset($objectreplaced->ref), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ReplacementInvoice") . ' : ' . $outputlangs->convToOutputCharset($objectreplaced->ref), '', 'R'); } if ($object->type == 2 && !empty($object->fk_facture_source)) { $objectreplaced = new Facture($this->db); @@ -1993,7 +2012,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy += 3; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("CorrectionInvoice").' : '.$outputlangs->convToOutputCharset($objectreplaced->ref), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("CorrectionInvoice") . ' : ' . $outputlangs->convToOutputCharset($objectreplaced->ref), '', 'R'); } $posy += 4; @@ -2001,15 +2020,15 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $pdf->SetTextColor(0, 0, 60); $title = $outputlangs->transnoentities("DateInvoice"); if (!empty($conf->global->PDF_USE_ALSO_LANGUAGE_CODE) && is_object($outputlangsbis)) { - $title .= ' - '.$outputlangsbis->transnoentities("DateInvoice"); + $title .= ' - ' . $outputlangsbis->transnoentities("DateInvoice"); } - $pdf->MultiCell($w, 3, $title." : ".dol_print_date($object->date, "day", false, $outputlangs, true), '', 'R'); + $pdf->MultiCell($w, 3, $title . " : " . dol_print_date($object->date, "day", false, $outputlangs, true), '', 'R'); if (!empty($conf->global->INVOICE_POINTOFTAX_DATE)) { $posy += 4; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("DatePointOfTax")." : ".dol_print_date($object->date_pointoftax, "day", false, $outputlangs), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("DatePointOfTax") . " : " . dol_print_date($object->date_pointoftax, "day", false, $outputlangs), '', 'R'); } if ($object->type != 2) { @@ -2018,16 +2037,16 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $pdf->SetTextColor(0, 0, 60); $title = $outputlangs->transnoentities("DateDue"); if (!empty($conf->global->PDF_USE_ALSO_LANGUAGE_CODE) && is_object($outputlangsbis)) { - $title .= ' - '.$outputlangsbis->transnoentities("DateDue"); + $title .= ' - ' . $outputlangsbis->transnoentities("DateDue"); } - $pdf->MultiCell($w, 3, $title." : ".dol_print_date($object->date_lim_reglement, "day", false, $outputlangs, true), '', 'R'); + $pdf->MultiCell($w, 3, $title . " : " . dol_print_date($object->date_lim_reglement, "day", false, $outputlangs, true), '', 'R'); } if (empty($conf->global->MAIN_PDF_HIDE_CUSTOMER_CODE) && $object->thirdparty->code_client) { $posy += 3; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("CustomerCode")." : ".$outputlangs->transnoentities($object->thirdparty->code_client), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("CustomerCode") . " : " . $outputlangs->transnoentities($object->thirdparty->code_client), '', 'R'); } // Get contact @@ -2039,7 +2058,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy += 4; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("SalesRepresentative")." : ".$usertmp->getFullName($langs), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("SalesRepresentative") . " : " . $usertmp->getFullName($langs), '', 'R'); } } @@ -2061,7 +2080,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output if (count($arrayidcontact) > 0) { $object->fetch_user($arrayidcontact[0]); $labelbeforecontactname = ($outputlangs->transnoentities("FromContactName") != 'FromContactName' ? $outputlangs->transnoentities("FromContactName") : $outputlangs->transnoentities("Name")); - $carac_emetteur .= ($carac_emetteur ? "\n" : '').$labelbeforecontactname." ".$outputlangs->convToOutputCharset($object->user->getFullName($outputlangs)); + $carac_emetteur .= ($carac_emetteur ? "\n" : '') . $labelbeforecontactname . " " . $outputlangs->convToOutputCharset($object->user->getFullName($outputlangs)); $carac_emetteur .= "\n"; } @@ -2166,11 +2185,11 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $contactshipping = $object->fetch_Contact($idaddressshipping[0]); $companystatic = new Societe($this->db); $companystatic->fetch($object->contact->fk_soc); - $carac_client_name_shipping=pdfBuildThirdpartyName($object->contact, $outputlangs); + $carac_client_name_shipping = pdfBuildThirdpartyName($object->contact, $outputlangs); $carac_client_shipping = pdf_build_address($outputlangs, $this->emetteur, $companystatic, $object->contact, $usecontact, 'target', $object); } else { - $carac_client_name_shipping=pdfBuildThirdpartyName($object->thirdparty, $outputlangs); - $carac_client_shipping=pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, '', 0, 'target', $object); + $carac_client_name_shipping = pdfBuildThirdpartyName($object->thirdparty, $outputlangs); + $carac_client_shipping = pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, '', 0, 'target', $object); } if (!empty($carac_client_shipping)) { $posy += $hautcadre; @@ -2189,7 +2208,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy = $pdf->getY(); // Show shipping information - $pdf->SetXY($posx+2, $posy); + $pdf->SetXY($posx + 2, $posy); $pdf->SetFont('', '', $default_font_size - 1); $pdf->MultiCell($widthrecbox - 2, 2, $carac_client_shipping, '', 'L'); $top_shift += $hautcadre; diff --git a/htdocs/core/modules/facture/doc/pdf_sponge.modules.php b/htdocs/core/modules/facture/doc/pdf_sponge.modules.php index 3db7d30cf297a..27ddbe70834fd 100644 --- a/htdocs/core/modules/facture/doc/pdf_sponge.modules.php +++ b/htdocs/core/modules/facture/doc/pdf_sponge.modules.php @@ -446,6 +446,19 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede $this->atleastonediscount++; } + // Do not take into account lines of the type “deposit.” + $is_deposit = false; + if (preg_match('/^\((.*)\)$/', $object->lines[$i]->desc, $reg)) { + if ($reg[1] == 'DEPOSIT') { + $is_deposit = true; + } + } + // If DEPOSIT, this line is completely ignored for calculations. + if ($is_deposit) { + continue; + } + + // determine category of operation if ($categoryOfOperation < 2) { $lineProductType = $object->lines[$i]->product_type; From 82c3849baa90cdd1a827dc5af7beeac664d0eb73 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 5 Dec 2025 16:21:15 +0100 Subject: [PATCH 031/172] Fix CI --- htdocs/product/stock/card.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/product/stock/card.php b/htdocs/product/stock/card.php index 3736b0296f7e2..20789ddf0e362 100644 --- a/htdocs/product/stock/card.php +++ b/htdocs/product/stock/card.php @@ -70,7 +70,7 @@ $backtopage = GETPOST('backtopage', 'alpha'); // Security check -$result=restrictedArea($user,'stock', $id, 'entrepot&stock'); +$result=restrictedArea($user, 'stock', $id, 'entrepot&stock'); // Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context $hookmanager->initHooks(array('warehousecard', 'globalcard')); From aa6ef5ae111e001be0e4c0dd52dcb764dfb34b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9?= Date: Fri, 5 Dec 2025 17:22:18 +0100 Subject: [PATCH 032/172] FIX: Missing Product ref in Bom stats --- htdocs/product/stats/bom.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/product/stats/bom.php b/htdocs/product/stats/bom.php index 66e673567a6f5..8dbca7894df83 100644 --- a/htdocs/product/stats/bom.php +++ b/htdocs/product/stats/bom.php @@ -230,7 +230,7 @@ $bomtmp->ref = $objp->ref; $product = new Product($db); if (!empty($objp->fk_product)) { - if (!array_key_exists($product->id, $product_cache)) { + if (!array_key_exists($objp->fk_product, $product_cache)) { $resultFetch = $product->fetch($objp->fk_product); if ($resultFetch < 0) { setEventMessages($product->error, $product->errors, 'errors'); From 23a01f78d09e2ff547fa5e9062494992179a1bd6 Mon Sep 17 00:00:00 2001 From: Alexandre SPANGARO Date: Tue, 9 Dec 2025 04:25:37 +0100 Subject: [PATCH 033/172] FIX #35375 Accountancy - SQL error on empty filter account category --- htdocs/accountancy/bookkeeping/list.php | 11 ++++++++--- htdocs/accountancy/bookkeeping/listbyaccount.php | 9 +++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/htdocs/accountancy/bookkeeping/list.php b/htdocs/accountancy/bookkeeping/list.php index 20b5256d17222..f939864d3345a 100644 --- a/htdocs/accountancy/bookkeeping/list.php +++ b/htdocs/accountancy/bookkeeping/list.php @@ -1,7 +1,7 @@ * Copyright (C) 2013-2016 Florian Henry - * Copyright (C) 2013-2024 Alexandre Spangaro + * Copyright (C) 2013-2025 Alexandre Spangaro * Copyright (C) 2022 Lionel Vessiller * Copyright (C) 2016-2017 Laurent Destailleur * Copyright (C) 2018-2024 Frédéric France @@ -49,7 +49,7 @@ */ // Load translation files required by the page -$langs->loadLangs(array("accountancy", "compta")); +$langs->loadLangs(array("accountancy", "categories", "compta")); // Get Parameters $socid = GETPOSTINT('socid'); @@ -372,7 +372,12 @@ $listofaccountsforgroup2[] = "'".$db->escape($tmpval['account_number'])."'"; } } - $filter['t.search_accounting_code_in'] = implode(',', $listofaccountsforgroup2); + if (!empty($listofaccountsforgroup2)) { + $filter['t.search_accounting_code_in'] = implode(',', $listofaccountsforgroup2); + } else { + $filter['t.search_accounting_code_in'] = "''"; + setEventMessages($langs->trans("ThisCategoryHasNoItems"), null, 'warnings'); + } $param .= '&search_account_category='.urlencode((string) ($search_account_category)); } if (!empty($search_accountancy_code)) { diff --git a/htdocs/accountancy/bookkeeping/listbyaccount.php b/htdocs/accountancy/bookkeeping/listbyaccount.php index 3c6da12d0f6db..6375d45e7b93d 100644 --- a/htdocs/accountancy/bookkeeping/listbyaccount.php +++ b/htdocs/accountancy/bookkeeping/listbyaccount.php @@ -47,7 +47,7 @@ */ // Load translation files required by the page -$langs->loadLangs(array("accountancy", "compta")); +$langs->loadLangs(array("accountancy", "categories", "compta")); $action = GETPOST('action', 'aZ09'); $socid = GETPOSTINT('socid'); @@ -328,7 +328,12 @@ $listofaccountsforgroup2[] = "'".$db->escape($tmpval['account_number'])."'"; } } - $filter['t.search_accounting_code_in'] = implode(',', $listofaccountsforgroup2); + if (!empty($listofaccountsforgroup2)) { + $filter['t.search_accounting_code_in'] = implode(',', $listofaccountsforgroup2); + } else { + $filter['t.search_accounting_code_in'] = "''"; + setEventMessages($langs->trans("ThisCategoryHasNoItems"), null, 'warnings'); + } $param .= '&search_account_category='.urlencode((string) ($search_account_category)); } if (!empty($search_accountancy_code_start)) { From 157aa32e0a59128cc757da382b6e1686c8dccacc Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Tue, 9 Dec 2025 10:12:10 +0100 Subject: [PATCH 034/172] FIX undefined variables on create invoice card from order (backport from v19) --- htdocs/compta/facture/card.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/compta/facture/card.php b/htdocs/compta/facture/card.php index 217270ce6e158..5c964d3e4febb 100644 --- a/htdocs/compta/facture/card.php +++ b/htdocs/compta/facture/card.php @@ -1779,8 +1779,8 @@ $lines[$i]->pa_ht, $label, $array_options, - $lines[$i]->situation_percent, - $lines[$i]->fk_prev_id, + $lines[$i]->situation_percent ?? 100, + $lines[$i]->fk_prev_id ?? 0, $lines[$i]->fk_unit, 0, '', From 70f7a21ad17a9b00da3602673f8f4d4b6cb4b574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20FRANCE?= Date: Tue, 9 Dec 2025 21:04:23 +0100 Subject: [PATCH 035/172] Update commonobject.class.php with same phpdoc than in develop --- htdocs/core/class/commonobject.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index a0489c7665306..a5a863a7573d4 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -491,7 +491,7 @@ abstract class CommonObject public $fk_delivery_address; /** - * @var int Shipping method ID + * @var ?int Shipping method ID * @see setShippingMethod() */ public $shipping_method_id; From 06f29c559e6c918362cca25ecce9b5f97a5bf278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20FRANCE?= Date: Tue, 9 Dec 2025 21:23:31 +0100 Subject: [PATCH 036/172] Update propal.class.php --- htdocs/comm/propal/class/propal.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/comm/propal/class/propal.class.php b/htdocs/comm/propal/class/propal.class.php index 0a32b3e311856..66bcc96ab16dc 100644 --- a/htdocs/comm/propal/class/propal.class.php +++ b/htdocs/comm/propal/class/propal.class.php @@ -270,12 +270,12 @@ class Propal extends CommonObject public $address; /** - * @var int availability ID + * @var ?int availability ID can be null in db */ public $availability_id; /** - * @var int availability ID + * @var ?int availability ID * @deprecated * @see $availability_id */ From 365c5b1015422b20ab272d70d31e0799c5de78f3 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 11 Dec 2025 00:50:37 +0100 Subject: [PATCH 037/172] Fix test on negative balance --- htdocs/holiday/class/holiday.class.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/htdocs/holiday/class/holiday.class.php b/htdocs/holiday/class/holiday.class.php index a661211b24254..9bcbb6c2f7d46 100644 --- a/htdocs/holiday/class/holiday.class.php +++ b/htdocs/holiday/class/holiday.class.php @@ -898,8 +898,9 @@ public function approve($user = null, $notrigger = 0) if ($checkBalance > 0) { $balance = $this->getCPforUser($this->fk_user, $this->fk_type); - $days = num_between_day($this->date_debut, $this->date_fin); - if ($balance - $days < 0 && getDolGlobalString('HOLIDAY_DISALLOW_NEGATIVE_BALANCE')) { + $days = num_public_holiday($this->date_debut, $this->date_fin); + + if ((($balance - $days) < 0) && getDolGlobalString('HOLIDAY_DISALLOW_NEGATIVE_BALANCE')) { $this->error = 'LeaveRequestCreationBlockedBecauseBalanceIsNegative'; return -1; } From 18f85df343b63e1310ea47b12b5a38f9f24a4208 Mon Sep 17 00:00:00 2001 From: Eric Seigne Date: Thu, 11 Dec 2025 10:11:58 +0100 Subject: [PATCH 038/172] remove spaces from prev fix --- .../modules/facture/doc/pdf_crabe.modules.php | 202 +++++++++--------- 1 file changed, 98 insertions(+), 104 deletions(-) diff --git a/htdocs/core/modules/facture/doc/pdf_crabe.modules.php b/htdocs/core/modules/facture/doc/pdf_crabe.modules.php index 587cb3cbe5770..ba6ff6f32d415 100644 --- a/htdocs/core/modules/facture/doc/pdf_crabe.modules.php +++ b/htdocs/core/modules/facture/doc/pdf_crabe.modules.php @@ -32,11 +32,11 @@ * \brief File of class to generate customers invoices from crabe model */ -require_once DOL_DOCUMENT_ROOT . '/core/modules/facture/modules_facture.php'; -require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php'; -require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php'; -require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php'; -require_once DOL_DOCUMENT_ROOT . '/core/lib/pdf.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/modules/facture/modules_facture.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php'; /** @@ -44,9 +44,9 @@ */ class pdf_crabe extends ModelePDFFactures { - /** - * @var DoliDb Database handler - */ + /** + * @var DoliDb Database handler + */ public $db; /** @@ -236,7 +236,7 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede // phpcs:enable global $user, $langs, $conf, $mysoc, $hookmanager, $nblines; - dol_syslog("write_file outputlangs->defaultlang=" . (is_object($outputlangs) ? $outputlangs->defaultlang : 'null')); + dol_syslog("write_file outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null')); if (!is_object($outputlangs)) { $outputlangs = $langs; @@ -275,14 +275,14 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede $objphoto = new Product($this->db); $objphoto->fetch($object->lines[$i]->fk_product); - $pdir = get_exdir($object->lines[$i]->fk_product, 2, 0, 0, $objphoto, 'product') . $object->lines[$i]->fk_product . "/photos/"; - $dir = $conf->product->dir_output . '/' . $pdir; + $pdir = get_exdir($object->lines[$i]->fk_product, 2, 0, 0, $objphoto, 'product').$object->lines[$i]->fk_product."/photos/"; + $dir = $conf->product->dir_output.'/'.$pdir; $realpath = ''; foreach ($objphoto->liste_photos($dir, 1) as $key => $obj) { $filename = $obj['photo']; //if ($obj['photo_vignette']) $filename='thumbs/'.$obj['photo_vignette']; - $realpath = $dir . $filename; + $realpath = $dir.$filename; break; } @@ -305,11 +305,11 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede // Definition of $dir and $file if ($object->specimen) { $dir = empty($conf->facture->multidir_output[$object->entity]) ? $conf->facture->dir_output : $conf->facture->multidir_output[$object->entity]; - $file = $dir . "/SPECIMEN.pdf"; + $file = $dir."/SPECIMEN.pdf"; } else { $objectref = dol_sanitizeFileName($object->ref); - $dir = (empty($conf->facture->multidir_output[$object->entity]) ? $conf->facture->dir_output : $conf->facture->multidir_output[$object->entity]) . "/" . $objectref; - $file = $dir . "/" . $objectref . ".pdf"; + $dir = (empty($conf->facture->multidir_output[$object->entity]) ? $conf->facture->dir_output : $conf->facture->multidir_output[$object->entity])."/".$objectref; + $file = $dir."/".$objectref.".pdf"; } if (!file_exists($dir)) { if (dol_mkdir($dir) < 0) { @@ -321,11 +321,11 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede if (file_exists($dir)) { // Add pdfgeneration hook if (!is_object($hookmanager)) { - include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php'; + include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php'; $hookmanager = new HookManager($this->db); } $hookmanager->initHooks(array('pdfgeneration')); - $parameters = array('file' => $file, 'object' => $object, 'outputlangs' => $outputlangs); + $parameters = array('file'=>$file, 'object'=>$object, 'outputlangs'=>$outputlangs); global $action; $reshook = $hookmanager->executeHooks('beforePDFCreation', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks @@ -369,7 +369,7 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede if (!empty($conf->mycompany->multidir_output[$object->entity])) { $logodir = $conf->mycompany->multidir_output[$object->entity]; } - $pagecount = $pdf->setSourceFile($logodir . '/' . $conf->global->MAIN_ADD_PDF_BACKGROUND); + $pagecount = $pdf->setSourceFile($logodir.'/'.$conf->global->MAIN_ADD_PDF_BACKGROUND); $tplidx = $pdf->importPage(1); } @@ -379,9 +379,9 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede $pdf->SetTitle($outputlangs->convToOutputCharset($object->ref)); $pdf->SetSubject($outputlangs->transnoentities("PdfInvoiceTitle")); - $pdf->SetCreator("Dolibarr " . DOL_VERSION); - $pdf->SetAuthor($mysoc->name . ($user->id > 0 ? ' - ' . $outputlangs->convToOutputCharset($user->getFullName($outputlangs)) : '')); - $pdf->SetKeyWords($outputlangs->convToOutputCharset($object->ref) . " " . $outputlangs->transnoentities("PdfInvoiceTitle") . " " . $outputlangs->convToOutputCharset($object->thirdparty->name)); + $pdf->SetCreator("Dolibarr ".DOL_VERSION); + $pdf->SetAuthor($mysoc->name.($user->id > 0 ? ' - '.$outputlangs->convToOutputCharset($user->getFullName($outputlangs)) : '')); + $pdf->SetKeyWords($outputlangs->convToOutputCharset($object->ref)." ".$outputlangs->transnoentities("PdfInvoiceTitle")." ".$outputlangs->convToOutputCharset($object->thirdparty->name)); if (getDolGlobalString('MAIN_DISABLE_PDF_COMPRESSION')) { $pdf->SetCompression(false); } @@ -419,7 +419,7 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede $this->atleastonediscount++; } - // Do not take into account lines of the type “deposit.” + // determine category of operation $is_deposit = false; if (preg_match('/^\((.*)\)$/', $object->lines[$i]->desc, $reg)) { if ($reg[1] == 'DEPOSIT') { @@ -526,7 +526,7 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede $parameters = array( 'object' => $object, 'i' => $i, - 'pdf' => &$pdf, + 'pdf' =>& $pdf, 'outputlangs' => $outputlangs, 'hidedetails' => $hidedetails ); @@ -794,8 +794,7 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede // Retrieve type from database for backward compatibility with old records if ((!isset($localtax1_type) || $localtax1_type == '' || !isset($localtax2_type) || $localtax2_type == '') // if tax type not defined - && (!empty($localtax1_rate) || !empty($localtax2_rate)) - ) { // and there is local tax + && (!empty($localtax1_rate) || !empty($localtax2_rate))) { // and there is local tax $localtaxtmp_array = getLocalTaxesFromRate($vatrate, 0, $object->thirdparty, $mysoc); $localtax1_type = isset($localtaxtmp_array[0]) ? $localtaxtmp_array[0] : ''; $localtax2_type = isset($localtaxtmp_array[2]) ? $localtaxtmp_array[2] : ''; @@ -827,10 +826,10 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede } $this->tva[$vatrate] += $tvaligne; // ->tva is abandonned, we use now ->tva_array that is more complete $vatcode = $object->lines[$i]->vat_src_code; - if (empty($this->tva_array[$vatrate . ($vatcode ? ' (' . $vatcode . ')' : '')]['amount'])) { - $this->tva_array[$vatrate . ($vatcode ? ' (' . $vatcode . ')' : '')]['amount'] = 0; + if (empty($this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'])) { + $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'] = 0; } - $this->tva_array[$vatrate . ($vatcode ? ' (' . $vatcode . ')' : '')] = array('vatrate' => $vatrate, 'vatcode' => $vatcode, 'amount' => $this->tva_array[$vatrate . ($vatcode ? ' (' . $vatcode . ')' : '')]['amount'] + $tvaligne); + $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')] = array('vatrate'=>$vatrate, 'vatcode'=>$vatcode, 'amount'=> $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'] + $tvaligne); if ($posYAfterImage > $posYAfterDescription) { $nexY = $posYAfterImage; @@ -839,10 +838,10 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede // Add line if (!empty($conf->global->MAIN_PDF_DASH_BETWEEN_LINES) && $i < ($nblines - 1)) { $pdf->setPage($pageposafter); - $pdf->SetLineStyle(array('dash' => '1,1', 'color' => array(80, 80, 80))); + $pdf->SetLineStyle(array('dash'=>'1,1', 'color'=>array(80, 80, 80))); //$pdf->SetDrawColor(190,190,200); $pdf->line($this->marge_gauche, $nexY + 1, $this->page_largeur - $this->marge_droite, $nexY + 1); - $pdf->SetLineStyle(array('dash' => 0)); + $pdf->SetLineStyle(array('dash'=>0)); } $nexY += 2; // Add space between lines @@ -895,7 +894,7 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede $this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforinfotot - $heightforfreetext - $heightforfooter, 0, $outputlangs, 1, 0, $object->multicurrency_code); $bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforfooter + 1; } - dol_syslog("bottomlasttab=" . $bottomlasttab . " this->page_hauteur=" . $this->page_hauteur . " heightforinfotot=" . $heightforinfotot . " heightforfreetext=" . $heightforfreetext . " heightforfooter=" . $heightforfooter); + dol_syslog("bottomlasttab=".$bottomlasttab." this->page_hauteur=".$this->page_hauteur." heightforinfotot=".$heightforinfotot." heightforfreetext=".$heightforfreetext." heightforfooter=".$heightforfooter); // Display info area $posy = $this->_tableau_info($pdf, $object, $bottomlasttab, $outputlangs, $outputlangsbis); @@ -927,7 +926,7 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede // Add pdfgeneration hook $hookmanager->initHooks(array('pdfgeneration')); - $parameters = array('file' => $file, 'object' => $object, 'outputlangs' => $outputlangs); + $parameters = array('file'=>$file, 'object'=>$object, 'outputlangs'=>$outputlangs); global $action; $reshook = $hookmanager->executeHooks('afterPDFCreation', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks if ($reshook < 0) { @@ -937,7 +936,7 @@ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidede dolChmod($file); - $this->result = array('fullpath' => $file); + $this->result = array('fullpath'=>$file); return 1; // No error } else { @@ -995,8 +994,8 @@ protected function _tableau_versements(&$pdf, $object, $posy, $outputlangs, $hei $sql = "SELECT re.rowid, re.amount_ht, re.multicurrency_amount_ht, re.amount_tva, re.multicurrency_amount_tva, re.amount_ttc, re.multicurrency_amount_ttc,"; $sql .= " re.description, re.fk_facture_source,"; $sql .= " f.type, f.datef"; - $sql .= " FROM " . MAIN_DB_PREFIX . "societe_remise_except as re, " . MAIN_DB_PREFIX . "facture as f"; - $sql .= " WHERE re.fk_facture_source = f.rowid AND re.fk_facture = " . ((int) $object->id); + $sql .= " FROM ".MAIN_DB_PREFIX."societe_remise_except as re, ".MAIN_DB_PREFIX."facture as f"; + $sql .= " WHERE re.fk_facture_source = f.rowid AND re.fk_facture = ".((int) $object->id); $resql = $this->db->query($sql); if ($resql) { $num = $this->db->num_rows($resql); @@ -1055,9 +1054,9 @@ protected function _tableau_versements(&$pdf, $object, $posy, $outputlangs, $hei // TODO Call getListOfPaymentsgetListOfPayments instead of hard coded sql $sql = "SELECT p.datep as date, p.fk_paiement, p.num_paiement as num, pf.amount as amount, pf.multicurrency_amount,"; $sql .= " cp.code"; - $sql .= " FROM " . MAIN_DB_PREFIX . "paiement_facture as pf, " . MAIN_DB_PREFIX . "paiement as p"; - $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_paiement as cp ON p.fk_paiement = cp.id"; - $sql .= " WHERE pf.fk_paiement = p.rowid AND pf.fk_facture = " . ((int) $object->id); + $sql .= " FROM ".MAIN_DB_PREFIX."paiement_facture as pf, ".MAIN_DB_PREFIX."paiement as p"; + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_paiement as cp ON p.fk_paiement = cp.id"; + $sql .= " WHERE pf.fk_paiement = p.rowid AND pf.fk_facture = ".((int) $object->id); //$sql.= " WHERE pf.fk_paiement = p.rowid AND pf.fk_facture = 1"; $sql .= " ORDER BY p.datep"; @@ -1089,7 +1088,7 @@ protected function _tableau_versements(&$pdf, $object, $posy, $outputlangs, $hei $pdf->SetXY($tab3_posx + 21, $tab3_top + $y); $pdf->MultiCell(20, 3, price($sign * ((isModEnabled("multicurrency") && $object->multicurrency_tx != 1) ? $row->multicurrency_amount : $row->amount), 0, $outputlangs), 0, 'L', 0); $pdf->SetXY($tab3_posx + 40, $tab3_top + $y); - $oper = $outputlangs->transnoentitiesnoconv("PaymentTypeShort" . $row->code); + $oper = $outputlangs->transnoentitiesnoconv("PaymentTypeShort".$row->code); $pdf->MultiCell(20, 3, $oper, 0, 'L', 0); $pdf->SetXY($tab3_posx + 58, $tab3_top + $y); @@ -1193,12 +1192,12 @@ protected function _tableau_info(&$pdf, $object, $posy, $outputlangs, $outputlan if ($object->type != 2 && ($object->cond_reglement_code || $object->cond_reglement)) { $pdf->SetFont('', 'B', $default_font_size - 2); $pdf->SetXY($this->marge_gauche, $posy); - $titre = $outputlangs->transnoentities("PaymentConditions") . ':'; + $titre = $outputlangs->transnoentities("PaymentConditions").':'; $pdf->MultiCell(43, 4, $titre, 0, 'L'); $pdf->SetFont('', '', $default_font_size - 2); $pdf->SetXY($posxval, $posy); - $lib_condition_paiement = $outputlangs->transnoentities("PaymentCondition" . $object->cond_reglement_code) != ('PaymentCondition' . $object->cond_reglement_code) ? $outputlangs->transnoentities("PaymentCondition" . $object->cond_reglement_code) : $outputlangs->convToOutputCharset($object->cond_reglement_doc ? $object->cond_reglement_doc : $object->cond_reglement_label); + $lib_condition_paiement = $outputlangs->transnoentities("PaymentCondition".$object->cond_reglement_code) != ('PaymentCondition'.$object->cond_reglement_code) ? $outputlangs->transnoentities("PaymentCondition".$object->cond_reglement_code) : $outputlangs->convToOutputCharset($object->cond_reglement_doc ? $object->cond_reglement_doc : $object->cond_reglement_label); $lib_condition_paiement = str_replace('\n', "\n", $lib_condition_paiement); $pdf->MultiCell(67, 4, $lib_condition_paiement, 0, 'L'); @@ -1209,7 +1208,7 @@ protected function _tableau_info(&$pdf, $object, $posy, $outputlangs, $outputlan if (getDolGlobalInt('INVOICE_CATEGORY_OF_OPERATION') == 2 && $this->categoryOfOperation >= 0) { $pdf->SetFont('', 'B', $default_font_size - 2); $pdf->SetXY($this->marge_gauche, $posy); - $categoryOfOperationTitle = $outputlangs->transnoentities("MentionCategoryOfOperations") . ' : '; + $categoryOfOperationTitle = $outputlangs->transnoentities("MentionCategoryOfOperations").' : '; $pdf->MultiCell($posxval - $this->marge_gauche, 4, $categoryOfOperationTitle, 0, 'L'); $pdf->SetFont('', '', $default_font_size - 2); @@ -1222,15 +1221,12 @@ protected function _tableau_info(&$pdf, $object, $posy, $outputlangs, $outputlan if ($object->type != 2) { // Check a payment mode is defined - if ( - empty($object->mode_reglement_code) - && !getDolGlobalInt('FACTURE_CHQ_NUMBER') - && !getDolGlobalInt('FACTURE_RIB_NUMBER') - ) { + if (empty($object->mode_reglement_code) + && !getDolGlobalInt('FACTURE_CHQ_NUMBER') + && !getDolGlobalInt('FACTURE_RIB_NUMBER')) { $this->error = $outputlangs->transnoentities("ErrorNoPaiementModeConfigured"); } elseif (($object->mode_reglement_code == 'CHQ' && !getDolGlobalInt('FACTURE_CHQ_NUMBER') && empty($object->fk_account) && empty($object->fk_bank)) - || ($object->mode_reglement_code == 'VIR' && !getDolGlobalInt('FACTURE_RIB_NUMBER') && empty($object->fk_account) && empty($object->fk_bank)) - ) { + || ($object->mode_reglement_code == 'VIR' && !getDolGlobalInt('FACTURE_RIB_NUMBER') && empty($object->fk_account) && empty($object->fk_bank))) { // Avoid having any valid PDF with setup that is not complete $outputlangs->load("errors"); @@ -1245,26 +1241,24 @@ protected function _tableau_info(&$pdf, $object, $posy, $outputlangs, $outputlan } // Show payment mode - if ( - !empty($object->mode_reglement_code) - && $object->mode_reglement_code != 'CHQ' - && $object->mode_reglement_code != 'VIR' - ) { + if (!empty($object->mode_reglement_code) + && $object->mode_reglement_code != 'CHQ' + && $object->mode_reglement_code != 'VIR') { $pdf->SetFont('', 'B', $default_font_size - 2); $pdf->SetXY($this->marge_gauche, $posy); - $titre = $outputlangs->transnoentities("PaymentMode") . ':'; + $titre = $outputlangs->transnoentities("PaymentMode").':'; $pdf->MultiCell(80, 5, $titre, 0, 'L'); $pdf->SetFont('', '', $default_font_size - 2); $pdf->SetXY($posxval, $posy); - $lib_mode_reg = $outputlangs->transnoentities("PaymentType" . $object->mode_reglement_code) != ('PaymentType' . $object->mode_reglement_code) ? $outputlangs->transnoentities("PaymentType" . $object->mode_reglement_code) : $outputlangs->convToOutputCharset($object->mode_reglement); + $lib_mode_reg = $outputlangs->transnoentities("PaymentType".$object->mode_reglement_code) != ('PaymentType'.$object->mode_reglement_code) ? $outputlangs->transnoentities("PaymentType".$object->mode_reglement_code) : $outputlangs->convToOutputCharset($object->mode_reglement); //#21654: add account number used for the debit if ($object->mode_reglement_code == "PRE") { - require_once DOL_DOCUMENT_ROOT . '/societe/class/companybankaccount.class.php'; + require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php'; $bac = new CompanyBankAccount($this->db); $bac->fetch(0, $object->thirdparty->id); - $iban = $bac->iban . (($bac->iban && $bac->bic) ? ' / ' : '') . $bac->bic; - $lib_mode_reg .= ' ' . $outputlangs->trans("PaymentTypePREdetails", dol_trunc($iban, 6, 'right', 'UTF-8', 1)); + $iban= $bac->iban.(($bac->iban && $bac->bic) ? ' / ' : '').$bac->bic; + $lib_mode_reg .= ' '.$outputlangs->trans("PaymentTypePREdetails", dol_trunc($iban, 6, 'right', 'UTF-8', 1)); } $pdf->MultiCell(80, 5, $lib_mode_reg, 0, 'L'); @@ -1299,13 +1293,13 @@ protected function _tableau_info(&$pdf, $object, $posy, $outputlangs, $outputlan } if ($object->statut != Facture::STATUS_DRAFT && $useonlinepayment) { - require_once DOL_DOCUMENT_ROOT . '/core/lib/payments.lib.php'; + require_once DOL_DOCUMENT_ROOT.'/core/lib/payments.lib.php'; global $langs; $langs->loadLangs(array('payment', 'paybox', 'stripe')); $servicename = $langs->transnoentities('Online'); $paiement_url = getOnlinePaymentUrl('', 'invoice', $object->ref, '', '', ''); - $linktopay = $langs->trans("ToOfferALinkForOnlinePayment", $servicename) . ' ' . $outputlangs->transnoentities("ClickHere") . ''; + $linktopay = $langs->trans("ToOfferALinkForOnlinePayment", $servicename).' '.$outputlangs->transnoentities("ClickHere").''; $pdf->SetXY($this->marge_gauche, $posy); $pdf->writeHTMLCell(80, 5, '', '', dol_htmlentitiesbr($linktopay), 0, 1); @@ -1428,7 +1422,7 @@ protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs // Total HT $pdf->SetFillColor(255, 255, 255); $pdf->SetXY($col1x, $tab2_top); - $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities(empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT) ? "TotalHT" : "Total") . (is_object($outputlangsbis) ? ' / ' . $outputlangsbis->transnoentities(empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT) ? "TotalHT" : "Total") : ''), 0, 'L', 1); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities(empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT) ? "TotalHT" : "Total").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities(empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT) ? "TotalHT" : "Total") : ''), 0, 'L', 1); $total_ht = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? $object->multicurrency_total_ht : $object->total_ht); $pdf->SetXY($col2x, $tab2_top); @@ -1463,12 +1457,12 @@ protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs $tvacompl = ''; if (preg_match('/\*/', $tvakey)) { $tvakey = str_replace('*', '', $tvakey); - $tvacompl = " (" . $outputlangs->transnoentities("NonPercuRecuperable") . ")"; + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; } - $totalvat = $outputlangs->transcountrynoentities("TotalLT1", $mysoc->country_code) . (is_object($outputlangsbis) ? ' / ' . $outputlangsbis->transcountrynoentities("TotalLT1", $mysoc->country_code) : ''); + $totalvat = $outputlangs->transcountrynoentities("TotalLT1", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalLT1", $mysoc->country_code) : ''); $totalvat .= ' '; - $totalvat .= vatrate(abs($tvakey), 1) . $tvacompl; + $totalvat .= vatrate(abs($tvakey), 1).$tvacompl; $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); @@ -1497,11 +1491,11 @@ protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs $tvacompl = ''; if (preg_match('/\*/', $tvakey)) { $tvakey = str_replace('*', '', $tvakey); - $tvacompl = " (" . $outputlangs->transnoentities("NonPercuRecuperable") . ")"; + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; } - $totalvat = $outputlangs->transcountrynoentities("TotalLT2", $mysoc->country_code) . (is_object($outputlangsbis) ? ' / ' . $outputlangsbis->transcountrynoentities("TotalLT2", $mysoc->country_code) : ''); + $totalvat = $outputlangs->transcountrynoentities("TotalLT2", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalLT2", $mysoc->country_code) : ''); $totalvat .= ' '; - $totalvat .= vatrate(abs($tvakey), 1) . $tvacompl; + $totalvat .= vatrate(abs($tvakey), 1).$tvacompl; $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); @@ -1525,16 +1519,16 @@ protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs $tvacompl = ''; if (preg_match('/\*/', $tvakey)) { $tvakey = str_replace('*', '', $tvakey); - $tvacompl = " (" . $outputlangs->transnoentities("NonPercuRecuperable") . ")"; + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; } - $totalvat = $outputlangs->transcountrynoentities("TotalVAT", $mysoc->country_code) . (is_object($outputlangsbis) ? ' / ' . $outputlangsbis->transcountrynoentities("TotalVAT", $mysoc->country_code) : ''); + $totalvat = $outputlangs->transcountrynoentities("TotalVAT", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalVAT", $mysoc->country_code) : ''); $totalvat .= ' '; if (getDolGlobalString('PDF_VAT_LABEL_IS_CODE_OR_RATE') == 'rateonly') { - $totalvat .= vatrate($tvaval['vatrate'], 1) . $tvacompl; + $totalvat .= vatrate($tvaval['vatrate'], 1).$tvacompl; } elseif (getDolGlobalString('PDF_VAT_LABEL_IS_CODE_OR_RATE') == 'codeonly') { - $totalvat .= $tvaval['vatcode'] . $tvacompl; + $totalvat .= $tvaval['vatcode'].$tvacompl; } else { - $totalvat .= vatrate($tvaval['vatrate'], 1) . ($tvaval['vatcode'] ? ' (' . $tvaval['vatcode'] . ')' : '') . $tvacompl; + $totalvat .= vatrate($tvaval['vatrate'], 1).($tvaval['vatcode'] ? ' ('.$tvaval['vatcode'].')' : '').$tvacompl; } $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); @@ -1561,10 +1555,10 @@ protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs $tvacompl = ''; if (preg_match('/\*/', $tvakey)) { $tvakey = str_replace('*', '', $tvakey); - $tvacompl = " (" . $outputlangs->transnoentities("NonPercuRecuperable") . ")"; + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; } - $totalvat = $outputlangs->transcountrynoentities("TotalLT1", $mysoc->country_code) . ' '; - $totalvat .= vatrate(abs($tvakey), 1) . $tvacompl; + $totalvat = $outputlangs->transcountrynoentities("TotalLT1", $mysoc->country_code).' '; + $totalvat .= vatrate(abs($tvakey), 1).$tvacompl; $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); @@ -1593,11 +1587,11 @@ protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs $tvacompl = ''; if (preg_match('/\*/', $tvakey)) { $tvakey = str_replace('*', '', $tvakey); - $tvacompl = " (" . $outputlangs->transnoentities("NonPercuRecuperable") . ")"; + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; } - $totalvat = $outputlangs->transcountrynoentities("TotalLT2", $mysoc->country_code) . ' '; + $totalvat = $outputlangs->transcountrynoentities("TotalLT2", $mysoc->country_code).' '; - $totalvat .= vatrate(abs($tvakey), 1) . $tvacompl; + $totalvat .= vatrate(abs($tvakey), 1).$tvacompl; $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); @@ -1648,8 +1642,8 @@ protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs $index++; $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); - $retainedWarrantyToPayOn = $outputlangs->transnoentities("RetainedWarranty") . ' (' . $object->retained_warranty . '%)'; - $retainedWarrantyToPayOn .= !empty($object->retained_warranty_date_limit) ? ' ' . $outputlangs->transnoentities("toPayOn", dol_print_date($object->retained_warranty_date_limit, 'day')) : ''; + $retainedWarrantyToPayOn = $outputlangs->transnoentities("RetainedWarranty").' ('.$object->retained_warranty.'%)'; + $retainedWarrantyToPayOn .= !empty($object->retained_warranty_date_limit) ? ' '.$outputlangs->transnoentities("toPayOn", dol_print_date($object->retained_warranty_date_limit, 'day')) : ''; $pdf->MultiCell($col2x - $col1x, $tab2_hl, $retainedWarrantyToPayOn, $useborder, 'L', 1); $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); @@ -1753,7 +1747,7 @@ protected function _tableau(&$pdf, $tab_top, $tab_height, $nexY, $outputlangs, $ $pdf->MultiCell(($pdf->GetStringWidth($categoryOfOperations)) + 4, 2, $categoryOfOperations); } - $titre = $outputlangs->transnoentities("AmountInCurrency", $outputlangs->transnoentitiesnoconv("Currency" . $currency)); + $titre = $outputlangs->transnoentities("AmountInCurrency", $outputlangs->transnoentitiesnoconv("Currency".$currency)); $pdf->SetXY($this->page_largeur - $this->marge_droite - ($pdf->GetStringWidth($titre) + 3), $tab_top - 4); $pdf->MultiCell(($pdf->GetStringWidth($titre) + 3), 2, $titre); @@ -1878,9 +1872,9 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $logodir = $conf->mycompany->multidir_output[$object->entity]; } if (!getDolGlobalInt('MAIN_PDF_USE_LARGE_LOGO')) { - $logo = $logodir . '/logos/thumbs/' . $this->emetteur->logo_small; + $logo = $logodir.'/logos/thumbs/'.$this->emetteur->logo_small; } else { - $logo = $logodir . '/logos/' . $this->emetteur->logo; + $logo = $logodir.'/logos/'.$this->emetteur->logo; } if (is_readable($logo)) { $height = pdf_getHeightForLogo($logo); @@ -1934,10 +1928,10 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $title .= $outputlangsbis->transnoentities("InvoiceProForma"); } } - $title .= ' ' . $outputlangs->convToOutputCharset($object->ref); + $title .= ' '.$outputlangs->convToOutputCharset($object->ref); if ($object->statut == $object::STATUS_DRAFT) { $pdf->SetTextColor(128, 0, 0); - $title .= ' - ' . $outputlangs->transnoentities("NotValidated"); + $title .= ' - '.$outputlangs->transnoentities("NotValidated"); } $pdf->MultiCell($w, 3, $title, '', 'R'); @@ -1962,7 +1956,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy += 4; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefCustomer") . " : " . $outputlangs->convToOutputCharset($object->ref_customer), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefCustomer")." : ".$outputlangs->convToOutputCharset($object->ref_customer), '', 'R'); } if (!empty($conf->global->PDF_SHOW_PROJECT_TITLE)) { @@ -1971,7 +1965,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy += 3; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("Project") . " : " . (empty($object->project->title) ? '' : $object->project->title), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("Project")." : ".(empty($object->project->title) ? '' : $object->project->title), '', 'R'); } } @@ -1982,7 +1976,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy += 3; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefProject") . " : " . (empty($object->project->ref) ? '' : $object->project->ref), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefProject")." : ".(empty($object->project->ref) ? '' : $object->project->ref), '', 'R'); } } @@ -1994,7 +1988,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy += 3; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ReplacementByInvoice") . ' : ' . $outputlangs->convToOutputCharset($objectreplacing->ref), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ReplacementByInvoice").' : '.$outputlangs->convToOutputCharset($objectreplacing->ref), '', 'R'); } if ($object->type == 1) { $objectreplaced = new Facture($this->db); @@ -2003,7 +1997,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy += 4; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ReplacementInvoice") . ' : ' . $outputlangs->convToOutputCharset($objectreplaced->ref), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ReplacementInvoice").' : '.$outputlangs->convToOutputCharset($objectreplaced->ref), '', 'R'); } if ($object->type == 2 && !empty($object->fk_facture_source)) { $objectreplaced = new Facture($this->db); @@ -2012,7 +2006,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy += 3; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("CorrectionInvoice") . ' : ' . $outputlangs->convToOutputCharset($objectreplaced->ref), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("CorrectionInvoice").' : '.$outputlangs->convToOutputCharset($objectreplaced->ref), '', 'R'); } $posy += 4; @@ -2020,15 +2014,15 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $pdf->SetTextColor(0, 0, 60); $title = $outputlangs->transnoentities("DateInvoice"); if (!empty($conf->global->PDF_USE_ALSO_LANGUAGE_CODE) && is_object($outputlangsbis)) { - $title .= ' - ' . $outputlangsbis->transnoentities("DateInvoice"); + $title .= ' - '.$outputlangsbis->transnoentities("DateInvoice"); } - $pdf->MultiCell($w, 3, $title . " : " . dol_print_date($object->date, "day", false, $outputlangs, true), '', 'R'); + $pdf->MultiCell($w, 3, $title." : ".dol_print_date($object->date, "day", false, $outputlangs, true), '', 'R'); if (!empty($conf->global->INVOICE_POINTOFTAX_DATE)) { $posy += 4; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("DatePointOfTax") . " : " . dol_print_date($object->date_pointoftax, "day", false, $outputlangs), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("DatePointOfTax")." : ".dol_print_date($object->date_pointoftax, "day", false, $outputlangs), '', 'R'); } if ($object->type != 2) { @@ -2037,16 +2031,16 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $pdf->SetTextColor(0, 0, 60); $title = $outputlangs->transnoentities("DateDue"); if (!empty($conf->global->PDF_USE_ALSO_LANGUAGE_CODE) && is_object($outputlangsbis)) { - $title .= ' - ' . $outputlangsbis->transnoentities("DateDue"); + $title .= ' - '.$outputlangsbis->transnoentities("DateDue"); } - $pdf->MultiCell($w, 3, $title . " : " . dol_print_date($object->date_lim_reglement, "day", false, $outputlangs, true), '', 'R'); + $pdf->MultiCell($w, 3, $title." : ".dol_print_date($object->date_lim_reglement, "day", false, $outputlangs, true), '', 'R'); } if (empty($conf->global->MAIN_PDF_HIDE_CUSTOMER_CODE) && $object->thirdparty->code_client) { $posy += 3; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("CustomerCode") . " : " . $outputlangs->transnoentities($object->thirdparty->code_client), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("CustomerCode")." : ".$outputlangs->transnoentities($object->thirdparty->code_client), '', 'R'); } // Get contact @@ -2058,7 +2052,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy += 4; $pdf->SetXY($posx, $posy); $pdf->SetTextColor(0, 0, 60); - $pdf->MultiCell($w, 3, $outputlangs->transnoentities("SalesRepresentative") . " : " . $usertmp->getFullName($langs), '', 'R'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("SalesRepresentative")." : ".$usertmp->getFullName($langs), '', 'R'); } } @@ -2080,7 +2074,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output if (count($arrayidcontact) > 0) { $object->fetch_user($arrayidcontact[0]); $labelbeforecontactname = ($outputlangs->transnoentities("FromContactName") != 'FromContactName' ? $outputlangs->transnoentities("FromContactName") : $outputlangs->transnoentities("Name")); - $carac_emetteur .= ($carac_emetteur ? "\n" : '') . $labelbeforecontactname . " " . $outputlangs->convToOutputCharset($object->user->getFullName($outputlangs)); + $carac_emetteur .= ($carac_emetteur ? "\n" : '').$labelbeforecontactname." ".$outputlangs->convToOutputCharset($object->user->getFullName($outputlangs)); $carac_emetteur .= "\n"; } @@ -2185,11 +2179,11 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $contactshipping = $object->fetch_Contact($idaddressshipping[0]); $companystatic = new Societe($this->db); $companystatic->fetch($object->contact->fk_soc); - $carac_client_name_shipping = pdfBuildThirdpartyName($object->contact, $outputlangs); + $carac_client_name_shipping=pdfBuildThirdpartyName($object->contact, $outputlangs); $carac_client_shipping = pdf_build_address($outputlangs, $this->emetteur, $companystatic, $object->contact, $usecontact, 'target', $object); } else { - $carac_client_name_shipping = pdfBuildThirdpartyName($object->thirdparty, $outputlangs); - $carac_client_shipping = pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, '', 0, 'target', $object); + $carac_client_name_shipping=pdfBuildThirdpartyName($object->thirdparty, $outputlangs); + $carac_client_shipping=pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, '', 0, 'target', $object); } if (!empty($carac_client_shipping)) { $posy += $hautcadre; @@ -2208,7 +2202,7 @@ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $output $posy = $pdf->getY(); // Show shipping information - $pdf->SetXY($posx + 2, $posy); + $pdf->SetXY($posx+2, $posy); $pdf->SetFont('', '', $default_font_size - 1); $pdf->MultiCell($widthrecbox - 2, 2, $carac_client_shipping, '', 'L'); $top_shift += $hautcadre; From 8d84cec3ca7718c77d825634be0c87e6889c422b Mon Sep 17 00:00:00 2001 From: Eric Seigne Date: Thu, 11 Dec 2025 10:43:24 +0100 Subject: [PATCH 039/172] remove slash like other parts of dolibarr code for TICKET_URL_PUBLIC_INTERFACE --- htdocs/ticket/class/ticket.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/ticket/class/ticket.class.php b/htdocs/ticket/class/ticket.class.php index 6d1ee87f93e8c..a18d23266dd95 100644 --- a/htdocs/ticket/class/ticket.class.php +++ b/htdocs/ticket/class/ticket.class.php @@ -2767,7 +2767,7 @@ public function newMessage($user, &$action, $private = 1, $public_area = 0) } // If public interface is not enable, use link to internal page into mail - $url_public_ticket = getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE', dol_buildpath('/public/ticket/view.php', 2)) . '/view.php?track_id='.$object->track_id; + $url_public_ticket = getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE', dol_buildpath('/public/ticket/view.php', 2)) . 'view.php?track_id='.$object->track_id; $message .= '
'.$langs->trans('TicketNewEmailBodyInfosTrackUrlCustomer').' : '.$object->track_id.'
'; // Build final message From 48bebca9654481ba3582cf642b3a69c8460be98b Mon Sep 17 00:00:00 2001 From: Braito Date: Sat, 13 Dec 2025 12:12:39 +0100 Subject: [PATCH 040/172] Check if lines are structured HTML before processing --- htdocs/core/class/html.formmail.class.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index 951bdd809288b..a93d70f4cbf15 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -956,6 +956,10 @@ public function get_form($addfileaction = 'addfile', $removefileaction = 'remove // Add lines substitution key from each line $lines = ''; $defaultlines = $arraydefaultmessage->content_lines; + $linesisstructuredhtml = false; + if (!empty($defaultlines) && dol_textishtml($defaultlines) && preg_match('/<\\s*(tr|td|th|table|tbody|thead|tfoot|ul|ol|li)\\b/i', $defaultlines)) { + $linesisstructuredhtml = true; + } if (isset($defaultlines)) { foreach ($this->substit_lines as $substit_line) { $lines .= make_substitutions($defaultlines, $substit_line)."\n"; @@ -989,6 +993,9 @@ public function get_form($addfileaction = 'addfile', $removefileaction = 'remove if (!dol_textishtml($this->substit['__SENDEREMAIL_SIGNATURE__'])) { $this->substit['__SENDEREMAIL_SIGNATURE__'] = dol_nl2br($this->substit['__SENDEREMAIL_SIGNATURE__']); } + if (!$linesisstructuredhtml) { + $this->substit['__LINES__'] = dol_nl2br($this->substit['__LINES__']); + } if (!dol_textishtml($this->substit['__ONLINE_PAYMENT_TEXT_AND_URL__'])) { $this->substit['__ONLINE_PAYMENT_TEXT_AND_URL__'] = dol_nl2br($this->substit['__ONLINE_PAYMENT_TEXT_AND_URL__']); } From 88760d71c39fcd471966800d59350e79886adc82 Mon Sep 17 00:00:00 2001 From: Braito Date: Sat, 13 Dec 2025 12:29:16 +0100 Subject: [PATCH 041/172] Fix line formatting for HTML content handling --- htdocs/core/class/html.formmail.class.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index a93d70f4cbf15..4b6aa9aa426e4 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -958,11 +958,11 @@ public function get_form($addfileaction = 'addfile', $removefileaction = 'remove $defaultlines = $arraydefaultmessage->content_lines; $linesisstructuredhtml = false; if (!empty($defaultlines) && dol_textishtml($defaultlines) && preg_match('/<\\s*(tr|td|th|table|tbody|thead|tfoot|ul|ol|li)\\b/i', $defaultlines)) { - $linesisstructuredhtml = true; + $linesisstructuredhtml = true; } if (isset($defaultlines)) { foreach ($this->substit_lines as $substit_line) { - $lines .= make_substitutions($defaultlines, $substit_line)."\n"; + $lines .= make_substitutions($defaultlines, $substit_line)."\n"; } } $this->substit['__LINES__'] = $lines; @@ -994,7 +994,7 @@ public function get_form($addfileaction = 'addfile', $removefileaction = 'remove $this->substit['__SENDEREMAIL_SIGNATURE__'] = dol_nl2br($this->substit['__SENDEREMAIL_SIGNATURE__']); } if (!$linesisstructuredhtml) { - $this->substit['__LINES__'] = dol_nl2br($this->substit['__LINES__']); + $this->substit['__LINES__'] = dol_nl2br($this->substit['__LINES__']); } if (!dol_textishtml($this->substit['__ONLINE_PAYMENT_TEXT_AND_URL__'])) { $this->substit['__ONLINE_PAYMENT_TEXT_AND_URL__'] = dol_nl2br($this->substit['__ONLINE_PAYMENT_TEXT_AND_URL__']); From 97043d0ce0edf17930845e9480447e5f66241e1f Mon Sep 17 00:00:00 2001 From: Braito Date: Sat, 13 Dec 2025 12:34:09 +0100 Subject: [PATCH 042/172] Fix HTML check for __LINES__ substitution --- htdocs/core/class/html.formmail.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index 4b6aa9aa426e4..b1f33634bc3a2 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -993,7 +993,7 @@ public function get_form($addfileaction = 'addfile', $removefileaction = 'remove if (!dol_textishtml($this->substit['__SENDEREMAIL_SIGNATURE__'])) { $this->substit['__SENDEREMAIL_SIGNATURE__'] = dol_nl2br($this->substit['__SENDEREMAIL_SIGNATURE__']); } - if (!$linesisstructuredhtml) { + if (!dol_textishtml($this->substit['__LINES__'])) { $this->substit['__LINES__'] = dol_nl2br($this->substit['__LINES__']); } if (!dol_textishtml($this->substit['__ONLINE_PAYMENT_TEXT_AND_URL__'])) { From da2d1e6e38f4a9e0738b35f5b8abbd5fb303d7fc Mon Sep 17 00:00:00 2001 From: Regis Houssin Date: Mon, 15 Dec 2025 10:41:22 +0100 Subject: [PATCH 043/172] FIX missing search extrafields input and title --- htdocs/projet/activity/permonth.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/htdocs/projet/activity/permonth.php b/htdocs/projet/activity/permonth.php index 804b4afc8f592..aeca89c863fdb 100644 --- a/htdocs/projet/activity/permonth.php +++ b/htdocs/projet/activity/permonth.php @@ -597,6 +597,11 @@ print ''; } print ''; +// TASK fields +$search_options_pattern = 'search_task_options_'; +$extrafieldsobjectkey = 'projet_task'; +$extrafieldsobjectprefix = 'efpt.'; +include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_input.tpl.php'; if (!empty($arrayfields['t.planned_workload']['checked'])) { print ''; } @@ -625,6 +630,10 @@ print ''.$langs->trans("ThirdParty").''; } print ''.$langs->trans("Task").''; +// TASK fields +$extrafieldsobjectkey = 'projet_task'; +$extrafieldsobjectprefix = 'efpt.'; +include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_title.tpl.php'; if (!empty($arrayfields['t.planned_workload']['checked'])) { print ''.$form->textwithpicto($langs->trans("PlannedWorkloadShort"), $langs->trans("PlannedWorkload")).''; } From ab19aa8d1386643ebaac8692ba146bc14538804f Mon Sep 17 00:00:00 2001 From: Regis Houssin Date: Mon, 15 Dec 2025 12:33:37 +0100 Subject: [PATCH 044/172] FIX avoid sql error with search option pattern --- htdocs/projet/activity/perweek.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/projet/activity/perweek.php b/htdocs/projet/activity/perweek.php index e07ea9d07ed57..6cd82c40a79c6 100644 --- a/htdocs/projet/activity/perweek.php +++ b/htdocs/projet/activity/perweek.php @@ -422,7 +422,7 @@ $extrafieldsobjectkey = 'projet_task'; include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php'; -$tasksarray = $taskstatic->getTasksArray(null, null, ($project->id ? $project->id : 0), $socid, 0, $search_project_ref, $onlyopenedproject, $morewherefilter, ($search_usertoprocessid ? $search_usertoprocessid : 0), 0, $extrafields, 0, $search_array_options_task, 1);// We want to see all tasks of open project i am allowed to see and that match filter, not only my tasks. Later only mine will be editable later. +$tasksarray = $taskstatic->getTasksArray(null, null, ($project->id ? $project->id : 0), $socid, 0, $search_project_ref, $onlyopenedproject, $morewherefilter, ($search_usertoprocessid ? $search_usertoprocessid : 0), 0, $extrafields, 0, array(), 1);// We want to see all tasks of open project i am allowed to see and that match filter, not only my tasks. Later only mine will be editable later. $tasksarraywithoutfilter = array(); if ($morewherefilter) { // Get all task without any filter, so we can show total of time spent for not visible tasks $tasksarraywithoutfilter = $taskstatic->getTasksArray(null, null, ($project->id ? $project->id : 0), $socid, 0, '', $onlyopenedproject, '', ($search_usertoprocessid ? $search_usertoprocessid : 0)); // We want to see all tasks of open project i am allowed to see and that match filter, not only my tasks. Later only mine will be editable later. From 89f254eb35b990c9202f5af41812164e374fe634 Mon Sep 17 00:00:00 2001 From: ATM-Lucas Date: Mon, 15 Dec 2025 15:21:57 +0100 Subject: [PATCH 045/172] Can Validated Holidays When Negative Balance --- htdocs/holiday/class/holiday.class.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/htdocs/holiday/class/holiday.class.php b/htdocs/holiday/class/holiday.class.php index 9bcbb6c2f7d46..9c7bc5e711325 100644 --- a/htdocs/holiday/class/holiday.class.php +++ b/htdocs/holiday/class/holiday.class.php @@ -775,7 +775,9 @@ public function validate($user = null, $notrigger = 0) if ($checkBalance > 0) { $balance = $this->getCPforUser($this->fk_user, $this->fk_type); - if ($balance < 0) { + + $days = num_between_day($this->date_debut, $this->date_fin); + if ($balance - $days < 0 && getDolGlobalString('HOLIDAY_DISALLOW_NEGATIVE_BALANCE')) { $this->error = 'LeaveRequestCreationBlockedBecauseBalanceIsNegative'; return -1; } From e40c6b7c5f07b01905341a80fe508bae59788ab6 Mon Sep 17 00:00:00 2001 From: ATM-Lucas Date: Mon, 15 Dec 2025 15:57:24 +0100 Subject: [PATCH 046/172] user num open day --- htdocs/holiday/class/holiday.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/holiday/class/holiday.class.php b/htdocs/holiday/class/holiday.class.php index 9c7bc5e711325..812063d3f42ba 100644 --- a/htdocs/holiday/class/holiday.class.php +++ b/htdocs/holiday/class/holiday.class.php @@ -776,7 +776,7 @@ public function validate($user = null, $notrigger = 0) if ($checkBalance > 0) { $balance = $this->getCPforUser($this->fk_user, $this->fk_type); - $days = num_between_day($this->date_debut, $this->date_fin); + $days = num_open_day($this->date_debut, $this->date_fin); if ($balance - $days < 0 && getDolGlobalString('HOLIDAY_DISALLOW_NEGATIVE_BALANCE')) { $this->error = 'LeaveRequestCreationBlockedBecauseBalanceIsNegative'; return -1; From cf0642e53ca45012865291c0eca47ec175c932b4 Mon Sep 17 00:00:00 2001 From: Braito Date: Mon, 15 Dec 2025 16:28:51 +0100 Subject: [PATCH 047/172] Remove unused variable 'linesisstructuredhtml' --- htdocs/core/class/html.formmail.class.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index b1f33634bc3a2..f1e664d4d39fb 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -956,13 +956,12 @@ public function get_form($addfileaction = 'addfile', $removefileaction = 'remove // Add lines substitution key from each line $lines = ''; $defaultlines = $arraydefaultmessage->content_lines; - $linesisstructuredhtml = false; if (!empty($defaultlines) && dol_textishtml($defaultlines) && preg_match('/<\\s*(tr|td|th|table|tbody|thead|tfoot|ul|ol|li)\\b/i', $defaultlines)) { $linesisstructuredhtml = true; } if (isset($defaultlines)) { foreach ($this->substit_lines as $substit_line) { - $lines .= make_substitutions($defaultlines, $substit_line)."\n"; + $lines .= make_substitutions($defaultlines, $substit_line)."\n"; } } $this->substit['__LINES__'] = $lines; From ab53b28c1b6f0c094fc7e0df4f930b22f040e5dd Mon Sep 17 00:00:00 2001 From: Braito Date: Mon, 15 Dec 2025 16:30:16 +0100 Subject: [PATCH 048/172] Simplify lines substitution logic Removed unnecessary check for structured HTML in default lines. --- htdocs/core/class/html.formmail.class.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index f1e664d4d39fb..2c0a7408f81df 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -956,9 +956,6 @@ public function get_form($addfileaction = 'addfile', $removefileaction = 'remove // Add lines substitution key from each line $lines = ''; $defaultlines = $arraydefaultmessage->content_lines; - if (!empty($defaultlines) && dol_textishtml($defaultlines) && preg_match('/<\\s*(tr|td|th|table|tbody|thead|tfoot|ul|ol|li)\\b/i', $defaultlines)) { - $linesisstructuredhtml = true; - } if (isset($defaultlines)) { foreach ($this->substit_lines as $substit_line) { $lines .= make_substitutions($defaultlines, $substit_line)."\n"; From bb7502d880e35aaa62f41df2f3dc84755419c4c7 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 16 Dec 2025 01:10:28 +0100 Subject: [PATCH 049/172] Fix condition check for negative balance --- htdocs/holiday/class/holiday.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/holiday/class/holiday.class.php b/htdocs/holiday/class/holiday.class.php index 812063d3f42ba..a66e9057937d4 100644 --- a/htdocs/holiday/class/holiday.class.php +++ b/htdocs/holiday/class/holiday.class.php @@ -777,7 +777,7 @@ public function validate($user = null, $notrigger = 0) $balance = $this->getCPforUser($this->fk_user, $this->fk_type); $days = num_open_day($this->date_debut, $this->date_fin); - if ($balance - $days < 0 && getDolGlobalString('HOLIDAY_DISALLOW_NEGATIVE_BALANCE')) { + if ((($balance - $days) < 0) && getDolGlobalString('HOLIDAY_DISALLOW_NEGATIVE_BALANCE')) { $this->error = 'LeaveRequestCreationBlockedBecauseBalanceIsNegative'; return -1; } From 6684ea70748dbd410f28beef016eb60374c59017 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Wed, 17 Dec 2025 14:27:51 +0100 Subject: [PATCH 050/172] fix perms in auguria --- htdocs/core/menus/init_menu_auguria.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/menus/init_menu_auguria.sql b/htdocs/core/menus/init_menu_auguria.sql index 98acc50b55c26..f8f7296c4a767 100644 --- a/htdocs/core/menus/init_menu_auguria.sql +++ b/htdocs/core/menus/init_menu_auguria.sql @@ -76,7 +76,7 @@ insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, left -- Home - Menu users and groups insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity, prefix) values ('', '1', __HANDLER__, 'left', 400__+MAX_llx_menu__, 'home', 'users', 1__+MAX_llx_menu__, '/user/home.php?mainmenu=home&leftmenu=users', 'MenuUsersAndGroups', 0, 'users', '', '', 2, 4, __ENTITY__, ''); -insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ('', '1', __HANDLER__, 'left', 401__+MAX_llx_menu__, 'home', 'users', 400__+MAX_llx_menu__, '', 'User', 1, 'users', '$user->hasRight("user", "user", "read") || $user->admin)', '', 2, 5, __ENTITY__); +insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ('', '1', __HANDLER__, 'left', 401__+MAX_llx_menu__, 'home', 'users', 400__+MAX_llx_menu__, '', 'User', 1, 'users', '($user->hasRight("user", "user", "read") || $user->admin)', '', 2, 5, __ENTITY__); insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ('', '1', __HANDLER__, 'left', 402__+MAX_llx_menu__, 'home', 'users', 401__+MAX_llx_menu__, '/user/card.php?mainmenu=home&leftmenu=users&action=create', 'NewUser', 2, 'users', '($user->hasRight("user","user","write") || $user->admin)', '', 2, 6, __ENTITY__); insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ('', '1', __HANDLER__, 'left', 403__+MAX_llx_menu__, 'home', 'users', 401__+MAX_llx_menu__, '/user/list.php?mainmenu=home&leftmenu=users', 'ListOfUsers', 2, 'users', '($user->hasRight("user","user","read") || $user->admin)', '', 2, 7, __ENTITY__); From eb4d9dec53450aa28d7404a1eecf9fc42118aaf6 Mon Sep 17 00:00:00 2001 From: Eric Seigne Date: Thu, 18 Dec 2025 09:36:45 +0100 Subject: [PATCH 051/172] thanks to lionel : remove view.php in default value like other part of dolibarr --- htdocs/ticket/class/ticket.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/ticket/class/ticket.class.php b/htdocs/ticket/class/ticket.class.php index a18d23266dd95..1ee21cca17df9 100644 --- a/htdocs/ticket/class/ticket.class.php +++ b/htdocs/ticket/class/ticket.class.php @@ -2767,7 +2767,7 @@ public function newMessage($user, &$action, $private = 1, $public_area = 0) } // If public interface is not enable, use link to internal page into mail - $url_public_ticket = getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE', dol_buildpath('/public/ticket/view.php', 2)) . 'view.php?track_id='.$object->track_id; + $url_public_ticket = getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE', dol_buildpath('/public/ticket/', 2)) . 'view.php?track_id='.$object->track_id; $message .= '
'.$langs->trans('TicketNewEmailBodyInfosTrackUrlCustomer').' : '.$object->track_id.'
'; // Build final message From ec46ff9b10f536ecfe33957b38b6e754bb34c6bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20FRANCE?= Date: Thu, 18 Dec 2025 13:28:16 +0100 Subject: [PATCH 052/172] fix search import key filter not working --- htdocs/comm/propal/list.php | 2 +- htdocs/commande/list.php | 2 +- htdocs/compta/facture/list.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/comm/propal/list.php b/htdocs/comm/propal/list.php index 79e32cc27d7ae..49aaf5658fb25 100644 --- a/htdocs/comm/propal/list.php +++ b/htdocs/comm/propal/list.php @@ -761,7 +761,7 @@ $sql .= " AND p.note_public LIKE '%".$db->escape($db->escapeforlike($search_note_public))."%'"; } if ($search_import_key) { - $sql .= natural_search("s.import_key", $search_import_key); + $sql .= natural_search("p.import_key", $search_import_key); } // Search on user if ($search_user > 0) { diff --git a/htdocs/commande/list.php b/htdocs/commande/list.php index 96a420adfec68..98c84cf55f03d 100644 --- a/htdocs/commande/list.php +++ b/htdocs/commande/list.php @@ -1103,7 +1103,7 @@ $sql .= " AND c.fk_input_reason = ".((int) $search_fk_input_reason); } if ($search_import_key) { - $sql .= natural_search("s.import_key", $search_import_key); + $sql .= natural_search("c.import_key", $search_import_key); } // Search on user if ($search_user > 0) { diff --git a/htdocs/compta/facture/list.php b/htdocs/compta/facture/list.php index 68e674e16325a..debcd32ec6d4a 100644 --- a/htdocs/compta/facture/list.php +++ b/htdocs/compta/facture/list.php @@ -898,7 +898,7 @@ $sql .= ' AND f.fk_fac_rec_source = ' . (int) $search_fk_fac_rec_source; } if ($search_import_key) { - $sql .= natural_search("s.import_key", $search_import_key); + $sql .= natural_search("f.import_key", $search_import_key); } // Search on user if ($search_user > 0) { From 3243126ab6c18cf4c53d93a50138e1a802d96748 Mon Sep 17 00:00:00 2001 From: Francis Appels Date: Sun, 21 Dec 2025 16:11:35 +0100 Subject: [PATCH 053/172] Fix getOutstandingBills regression from #35266 --- htdocs/societe/class/societe.class.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/htdocs/societe/class/societe.class.php b/htdocs/societe/class/societe.class.php index f2c9efdc3b339..03d081b8d1952 100644 --- a/htdocs/societe/class/societe.class.php +++ b/htdocs/societe/class/societe.class.php @@ -4863,6 +4863,8 @@ public function getOutstandingOrders($mode = 'customer') */ public function getOutstandingBills($mode = 'customer', $late = 0) { + include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; + $table = 'facture'; if ($mode == 'supplier') { $table = 'facture_fourn'; From c6a6d14627757be838c49993c2a296965d904893 Mon Sep 17 00:00:00 2001 From: ATM-Lucas Date: Mon, 22 Dec 2025 12:08:00 +0100 Subject: [PATCH 054/172] False balance --- htdocs/holiday/class/holiday.class.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/htdocs/holiday/class/holiday.class.php b/htdocs/holiday/class/holiday.class.php index a66e9057937d4..2948b99298852 100644 --- a/htdocs/holiday/class/holiday.class.php +++ b/htdocs/holiday/class/holiday.class.php @@ -775,9 +775,9 @@ public function validate($user = null, $notrigger = 0) if ($checkBalance > 0) { $balance = $this->getCPforUser($this->fk_user, $this->fk_type); + $daysAsked = num_open_day($this->date_debut, $this->date_fin, 0, 1); - $days = num_open_day($this->date_debut, $this->date_fin); - if ((($balance - $days) < 0) && getDolGlobalString('HOLIDAY_DISALLOW_NEGATIVE_BALANCE')) { + if (($balance - $daysAsked) < 0 && getDolGlobalString('HOLIDAY_DISALLOW_NEGATIVE_BALANCE')) { $this->error = 'LeaveRequestCreationBlockedBecauseBalanceIsNegative'; return -1; } @@ -899,10 +899,9 @@ public function approve($user = null, $notrigger = 0) if ($checkBalance > 0) { $balance = $this->getCPforUser($this->fk_user, $this->fk_type); + $daysAsked = num_open_day($this->date_debut, $this->date_fin, 0, 1); - $days = num_public_holiday($this->date_debut, $this->date_fin); - - if ((($balance - $days) < 0) && getDolGlobalString('HOLIDAY_DISALLOW_NEGATIVE_BALANCE')) { + if (($balance - $daysAsked) < 0 && getDolGlobalString('HOLIDAY_DISALLOW_NEGATIVE_BALANCE')) { $this->error = 'LeaveRequestCreationBlockedBecauseBalanceIsNegative'; return -1; } @@ -1029,8 +1028,9 @@ public function update($user = null, $notrigger = 0) if ($checkBalance > 0 && $this->status != self::STATUS_DRAFT) { $balance = $this->getCPforUser($this->fk_user, $this->fk_type); + $daysAsked = num_open_day($this->date_debut, $this->date_fin, 0, 1); - if ($balance < 0) { + if (($balance - $daysAsked) < 0 && getDolGlobalString('HOLIDAY_DISALLOW_NEGATIVE_BALANCE')) { $this->error = 'LeaveRequestCreationBlockedBecauseBalanceIsNegative'; return -1; } From 2e22f9e75afcd264162275c24411513232eea087 Mon Sep 17 00:00:00 2001 From: Eric - CAP-REL <1468823+rycks@users.noreply.github.com> Date: Sat, 27 Dec 2025 23:04:40 +0100 Subject: [PATCH 055/172] fix concat for a undef entry of array (#36754) --- htdocs/core/class/timespent.class.php | 2 +- htdocs/modulebuilder/template/class/myobject.class.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/class/timespent.class.php b/htdocs/core/class/timespent.class.php index 256077fa40fa1..711aa8e25e905 100755 --- a/htdocs/core/class/timespent.class.php +++ b/htdocs/core/class/timespent.class.php @@ -725,7 +725,7 @@ public function getTooltipContentArray($params) if (isset($this->status)) { $datas['picto'] .= ' '.$this->getLibStatut(5); } - $datas['ref'] .= '
'.$langs->trans('Ref').': '.$this->ref; + $datas['ref'] = '
'.$langs->trans('Ref').': '.$this->ref; return $datas; } diff --git a/htdocs/modulebuilder/template/class/myobject.class.php b/htdocs/modulebuilder/template/class/myobject.class.php index b4f41e00b2f85..52587f1b7d917 100644 --- a/htdocs/modulebuilder/template/class/myobject.class.php +++ b/htdocs/modulebuilder/template/class/myobject.class.php @@ -774,7 +774,7 @@ public function getTooltipContentArray($params) if (isset($this->status)) { $datas['picto'] .= ' '.$this->getLibStatut(5); } - $datas['ref'] .= '
'.$langs->trans('Ref').': '.$this->ref; + $datas['ref'] = '
'.$langs->trans('Ref').': '.$this->ref; return $datas; } From c33b7e0bf1d163673ce4cd9398b71eed72a53dd6 Mon Sep 17 00:00:00 2001 From: lvessiller-opendsi Date: Tue, 30 Dec 2025 11:03:20 +0100 Subject: [PATCH 056/172] FIX finished regex in product import (#36770) --- htdocs/core/modules/modProduct.class.php | 1 - 1 file changed, 1 deletion(-) diff --git a/htdocs/core/modules/modProduct.class.php b/htdocs/core/modules/modProduct.class.php index e5b0473597b85..9ae50528f6f72 100644 --- a/htdocs/core/modules/modProduct.class.php +++ b/htdocs/core/modules/modProduct.class.php @@ -656,7 +656,6 @@ public function __construct($db) 'p.fk_product_type'=>'^[0|1]$', 'p.datec'=>'^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$', 'p.recuperableonly' => '^[0|1]$', - 'p.finished' => '^[0|1]$' ); // field order as per structure of table llx_product $import_sample = array( From 70348f8c85173c2be57b6e7425b8595c86741117 Mon Sep 17 00:00:00 2001 From: atm-lucas <121817516+atm-lucasmantegari@users.noreply.github.com> Date: Tue, 30 Dec 2025 11:09:38 +0100 Subject: [PATCH 057/172] Supplier Proposal line option (#36753) --- htdocs/fourn/commande/card.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/fourn/commande/card.php b/htdocs/fourn/commande/card.php index 29f547e5da15d..74fea9a945be7 100644 --- a/htdocs/fourn/commande/card.php +++ b/htdocs/fourn/commande/card.php @@ -1311,7 +1311,7 @@ $num = count($lines); for ($i = 0; $i < $num; $i++) { - if (empty($lines[$i]->subprice) || $lines[$i]->qty <= 0 || !in_array($lines[$i]->id, $selectedLines)) { + if (empty($lines[$i]->subprice) || $lines[$i]->qty < 0 || !in_array($lines[$i]->id, $selectedLines)) { continue; } From a648ec6670f4a5faa785b41dd65c6bfc26267b53 Mon Sep 17 00:00:00 2001 From: ThomasNgr-OpenDSI Date: Mon, 5 Jan 2026 17:55:11 +0100 Subject: [PATCH 058/172] FIX: inventory qty is not saved if page > 1 (#36804) * FIX: inventory quantities were not saved when on page != 1 * Add sorting by batch number --- htdocs/product/inventory/inventory.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/htdocs/product/inventory/inventory.php b/htdocs/product/inventory/inventory.php index 31700b8222794..494fb9d082247 100644 --- a/htdocs/product/inventory/inventory.php +++ b/htdocs/product/inventory/inventory.php @@ -89,6 +89,10 @@ $sortorder = "ASC"; } +// Sort by warehouse/product or product/warehouse, then by batch. +$sortfield .= ',' . ($sortfield == 'e.ref' ? 'p.ref' : 'e.ref') . ',id.batch,id.rowid'; +$sortorder .= ',' . $sortorder.",ASC,ASC"; + // Fetch optionals attributes and labels $extrafields->fetch_name_optionals_label($object->table_element); @@ -291,8 +295,10 @@ $sql = 'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,'; $sql .= ' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated'; $sql .= ' FROM '.MAIN_DB_PREFIX.'inventorydet as id'; + $sql .= ' LEFT JOIN ' . $db->prefix() . 'product as p ON id.fk_product = p.rowid'; + $sql .= ' LEFT JOIN ' . $db->prefix() . 'entrepot as e ON id.fk_warehouse = e.rowid'; $sql .= ' WHERE id.fk_inventory = '.((int) $object->id); - $sql .= $db->order('id.rowid', 'ASC'); + $sql .= $db->order($sortfield, $sortorder); $sql .= $db->plimit($limit, $offset); $db->begin(); @@ -1037,10 +1043,6 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,sele print ''; } -// Sort by warehouse/product or product/warehouse -$sortfield .= ',' . ($sortfield == 'e.ref' ? 'p.ref' : 'e.ref'); -$sortorder .= ',' . $sortorder; - // Request to show lines of inventory (prefilled after start/validate step) $sql = 'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,'; $sql .= ' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated, id.fk_movement, id.pmp_real, id.pmp_expected'; From bfe8f57c03293dcc92cdad630c253629505e75ef Mon Sep 17 00:00:00 2001 From: Maxime Kohlhaas Date: Mon, 5 Jan 2026 18:44:55 +0100 Subject: [PATCH 059/172] Fix : updateline was not possible because of wrong status test (#36802) * Fix : updateline was not possible because of wrong status test * Fix : better fix, remove useless test to work like on customer facrec --- .../class/fournisseur.facture-rec.class.php | 469 +++++++++--------- 1 file changed, 230 insertions(+), 239 deletions(-) diff --git a/htdocs/fourn/class/fournisseur.facture-rec.class.php b/htdocs/fourn/class/fournisseur.facture-rec.class.php index 3927fe1ae1dd7..5bb7d9359812a 100644 --- a/htdocs/fourn/class/fournisseur.facture-rec.class.php +++ b/htdocs/fourn/class/fournisseur.facture-rec.class.php @@ -899,152 +899,146 @@ public function addline($fk_product, $ref, $label, $desc, $pu_ht, $pu_ttc, $qty, return -1; } - if ($this->suspended == self::STATUS_NOTSUSPENDED) { - $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc); - - // Clean vat code - $reg = array(); - $vat_src_code = ''; - if (preg_match('/\((.*)\)/', $txtva, $reg)) { - $vat_src_code = $reg[1]; - $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate. - } - - // Clean parameters - $fk_product = empty($fk_product) ? 0 : $fk_product; - $label = empty($label) ? '' : $label; - $remise_percent = empty($remise_percent) ? 0 : price2num($remise_percent); - $qty = price2num($qty); - $pu_ht = price2num($pu_ht); - $pu_ttc = price2num($pu_ttc); - if (!preg_match('/\((.*)\)/', $txtva)) { - $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5' - } - $txlocaltax1 = price2num($txlocaltax1); - $txlocaltax2 = price2num($txlocaltax2); - $txtva = !empty($txtva) ? $txtva : 0; - $txlocaltax1 = !empty($txlocaltax1) ? $txlocaltax1 : 0; - $txlocaltax2 = !empty($txlocaltax2) ? $txlocaltax2 : 0; - $info_bits = !empty($info_bits) ? $info_bits : 0; - $info_bits = !empty($info_bits) ? $info_bits : 0; - $pu = $price_base_type == 'HT' ? $pu_ht : $pu_ttc; - - // Calcul du total TTC et de la TVA pour la ligne a partir de qty, pu, remise_percent et txtva - // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker - // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva. - - $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise); - $total_ht = $tabprice[0]; - $total_tva = $tabprice[1]; - $total_ttc = $tabprice[2]; - $total_localtax1 = $tabprice[9]; - $total_localtax2 = $tabprice[10]; - $pu_ht = $tabprice[3]; - - // MultiCurrency - $multicurrency_total_ht = $tabprice[16]; - $multicurrency_total_tva = $tabprice[17]; - $multicurrency_total_ttc = $tabprice[18]; - $pu_ht_devise = $tabprice[19]; - - $this->db->begin(); - $product_type = $type; - if ($fk_product) { - $product = new Product($this->db); - $result = $product->fetch($fk_product); - if ($result < 0) { - return -1; - } - $product_type = $product->type; - if (empty($label)) { - $label = $product->label; - } - } - - $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . 'facture_fourn_det_rec ('; - $sql .= 'fk_facture_fourn'; - $sql .= ', fk_product'; - $sql .= ', ref'; - $sql .= ', label'; - $sql .= ', description'; - $sql .= ', pu_ht'; - $sql .= ', pu_ttc'; - $sql .= ', qty'; - $sql .= ', remise_percent'; - $sql .= ', fk_remise_except'; - $sql .= ', vat_src_code'; - $sql .= ', tva_tx'; - $sql .= ', localtax1_tx'; - $sql .= ', localtax1_type'; - $sql .= ', localtax2_tx'; - $sql .= ', localtax2_type'; - $sql .= ', total_ht'; - $sql .= ', total_tva'; - $sql .= ', total_localtax1'; - $sql .= ', total_localtax2'; - $sql .= ', total_ttc'; - $sql .= ', product_type'; - $sql .= ', date_start'; - $sql .= ', date_end'; - $sql .= ', info_bits'; - $sql .= ', special_code'; - $sql .= ', rang'; - $sql .= ', fk_unit'; - $sql .= ', fk_user_author'; - $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc'; - $sql .= ') VALUES ('; - $sql .= ' ' . (int) $facid; // source supplier invoice id - $sql .= ', ' . (!empty($fk_product) ? "'" . $this->db->escape($fk_product) . "'" : 'null'); - $sql .= ', ' . (!empty($ref) ? "'" . $this->db->escape($ref) . "'" : 'null'); - $sql .= ', ' . (!empty($label) ? "'" . $this->db->escape($label) . "'" : 'null'); - $sql .= ", '" . $this->db->escape($desc) . "'"; - $sql .= ', ' . price2num($pu_ht); - $sql .= ', ' . price2num($pu_ttc); - $sql .= ', ' . price2num($qty); - $sql .= ', ' . price2num($remise_percent); - $sql .= ', null'; - $sql .= ", '" . $this->db->escape($vat_src_code) . "'"; - $sql .= ', ' . price2num($txtva); - $sql .= ', ' . price2num($txlocaltax1); - $sql .= ", '" . $this->db->escape(isset($localtaxes_type[0]) ? $localtaxes_type[0] : '') . "'"; - $sql .= ', ' . price2num($txlocaltax2); - $sql .= ", '" . $this->db->escape(isset($localtaxes_type[2]) ? $localtaxes_type[2] : '') . "'"; - $sql .= ', ' . price2num($total_ht); - $sql .= ', ' . price2num($total_tva); - $sql .= ', ' . price2num($total_localtax1); - $sql .= ', ' . price2num($total_localtax2); - $sql .= ', ' . price2num($total_ttc); - $sql .= ', ' . (int) $product_type; - $sql .= ', ' . ($date_start > 0 ? (int) $date_start : 'NULL'); - $sql .= ', ' . ($date_end > 0 ? (int) $date_end : 'NULL'); - $sql .= ', ' . (int) $info_bits; - $sql .= ', ' . (int) $special_code; - $sql .= ', ' . (int) $rang; - $sql .= ', ' . ($fk_unit ? (int) $fk_unit : 'NULL'); - $sql .= ', ' . (int) $user->id; - $sql .= ', ' . (int) $this->fk_multicurrency; - $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'"; - $sql .= ', ' . price2num($pu_ht_devise, 'CU'); - $sql .= ', ' . price2num($multicurrency_total_ht, 'CT'); - $sql .= ', ' . price2num($multicurrency_total_tva, 'CT'); - $sql .= ', ' . price2num($multicurrency_total_ttc, 'CT'); - $sql .= ')'; + $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc); + + // Clean vat code + $reg = array(); + $vat_src_code = ''; + if (preg_match('/\((.*)\)/', $txtva, $reg)) { + $vat_src_code = $reg[1]; + $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate. + } - dol_syslog(get_class($this). '::addline', LOG_DEBUG); - if ($this->db->query($sql)) { - $lineId = $this->db->last_insert_id(MAIN_DB_PREFIX. 'facture_fourn_det_rec'); - $this->update_price(); - $this->id = $facid; - $this->db->commit(); - return $lineId; - } else { - $this->db->rollback(); - $this->error = $this->db->lasterror(); + // Clean parameters + $fk_product = empty($fk_product) ? 0 : $fk_product; + $label = empty($label) ? '' : $label; + $remise_percent = empty($remise_percent) ? 0 : price2num($remise_percent); + $qty = price2num($qty); + $pu_ht = price2num($pu_ht); + $pu_ttc = price2num($pu_ttc); + if (!preg_match('/\((.*)\)/', $txtva)) { + $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5' + } + $txlocaltax1 = price2num($txlocaltax1); + $txlocaltax2 = price2num($txlocaltax2); + $txtva = !empty($txtva) ? $txtva : 0; + $txlocaltax1 = !empty($txlocaltax1) ? $txlocaltax1 : 0; + $txlocaltax2 = !empty($txlocaltax2) ? $txlocaltax2 : 0; + $info_bits = !empty($info_bits) ? $info_bits : 0; + $info_bits = !empty($info_bits) ? $info_bits : 0; + $pu = $price_base_type == 'HT' ? $pu_ht : $pu_ttc; + + // Calcul du total TTC et de la TVA pour la ligne a partir de qty, pu, remise_percent et txtva + // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker + // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva. + + $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise); + $total_ht = $tabprice[0]; + $total_tva = $tabprice[1]; + $total_ttc = $tabprice[2]; + $total_localtax1 = $tabprice[9]; + $total_localtax2 = $tabprice[10]; + $pu_ht = $tabprice[3]; + + // MultiCurrency + $multicurrency_total_ht = $tabprice[16]; + $multicurrency_total_tva = $tabprice[17]; + $multicurrency_total_ttc = $tabprice[18]; + $pu_ht_devise = $tabprice[19]; + $this->db->begin(); + $product_type = $type; + if ($fk_product) { + $product = new Product($this->db); + $result = $product->fetch($fk_product); + if ($result < 0) { return -1; } + $product_type = $product->type; + if (empty($label)) { + $label = $product->label; + } + } + + $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . 'facture_fourn_det_rec ('; + $sql .= 'fk_facture_fourn'; + $sql .= ', fk_product'; + $sql .= ', ref'; + $sql .= ', label'; + $sql .= ', description'; + $sql .= ', pu_ht'; + $sql .= ', pu_ttc'; + $sql .= ', qty'; + $sql .= ', remise_percent'; + $sql .= ', fk_remise_except'; + $sql .= ', vat_src_code'; + $sql .= ', tva_tx'; + $sql .= ', localtax1_tx'; + $sql .= ', localtax1_type'; + $sql .= ', localtax2_tx'; + $sql .= ', localtax2_type'; + $sql .= ', total_ht'; + $sql .= ', total_tva'; + $sql .= ', total_localtax1'; + $sql .= ', total_localtax2'; + $sql .= ', total_ttc'; + $sql .= ', product_type'; + $sql .= ', date_start'; + $sql .= ', date_end'; + $sql .= ', info_bits'; + $sql .= ', special_code'; + $sql .= ', rang'; + $sql .= ', fk_unit'; + $sql .= ', fk_user_author'; + $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc'; + $sql .= ') VALUES ('; + $sql .= ' ' . (int) $facid; // source supplier invoice id + $sql .= ', ' . (!empty($fk_product) ? "'" . $this->db->escape($fk_product) . "'" : 'null'); + $sql .= ', ' . (!empty($ref) ? "'" . $this->db->escape($ref) . "'" : 'null'); + $sql .= ', ' . (!empty($label) ? "'" . $this->db->escape($label) . "'" : 'null'); + $sql .= ", '" . $this->db->escape($desc) . "'"; + $sql .= ', ' . price2num($pu_ht); + $sql .= ', ' . price2num($pu_ttc); + $sql .= ', ' . price2num($qty); + $sql .= ', ' . price2num($remise_percent); + $sql .= ', null'; + $sql .= ", '" . $this->db->escape($vat_src_code) . "'"; + $sql .= ', ' . price2num($txtva); + $sql .= ', ' . price2num($txlocaltax1); + $sql .= ", '" . $this->db->escape(isset($localtaxes_type[0]) ? $localtaxes_type[0] : '') . "'"; + $sql .= ', ' . price2num($txlocaltax2); + $sql .= ", '" . $this->db->escape(isset($localtaxes_type[2]) ? $localtaxes_type[2] : '') . "'"; + $sql .= ', ' . price2num($total_ht); + $sql .= ', ' . price2num($total_tva); + $sql .= ', ' . price2num($total_localtax1); + $sql .= ', ' . price2num($total_localtax2); + $sql .= ', ' . price2num($total_ttc); + $sql .= ', ' . (int) $product_type; + $sql .= ', ' . ($date_start > 0 ? (int) $date_start : 'NULL'); + $sql .= ', ' . ($date_end > 0 ? (int) $date_end : 'NULL'); + $sql .= ', ' . (int) $info_bits; + $sql .= ', ' . (int) $special_code; + $sql .= ', ' . (int) $rang; + $sql .= ', ' . ($fk_unit ? (int) $fk_unit : 'NULL'); + $sql .= ', ' . (int) $user->id; + $sql .= ', ' . (int) $this->fk_multicurrency; + $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'"; + $sql .= ', ' . price2num($pu_ht_devise, 'CU'); + $sql .= ', ' . price2num($multicurrency_total_ht, 'CT'); + $sql .= ', ' . price2num($multicurrency_total_tva, 'CT'); + $sql .= ', ' . price2num($multicurrency_total_ttc, 'CT'); + $sql .= ')'; + + dol_syslog(get_class($this). '::addline', LOG_DEBUG); + if ($this->db->query($sql)) { + $lineId = $this->db->last_insert_id(MAIN_DB_PREFIX. 'facture_fourn_det_rec'); + $this->update_price(); + $this->id = $facid; + $this->db->commit(); + return $lineId; } else { - $this->error = 'Recurring Invoice is suspended. adding lines not allowed.'; + $this->db->rollback(); + $this->error = $this->db->lasterror(); return -1; } @@ -1091,113 +1085,110 @@ public function updateline($rowid, $fk_product, $ref, $label, $desc, $pu_ht, $qt return -1; } - if ($this->status == self::STATUS_SUSPENDED) { - // Clean parameters - $fk_product = empty($fk_product) ? 0 : $fk_product; - $label = empty($label) ? '' : $label; - $remise_percent = empty($remise_percent) ? 0 : price2num($remise_percent); - $qty = price2num($qty); - $info_bits = empty($info_bits) ? 0 : $info_bits; - $pu_ht = price2num($pu_ht); - $pu_ttc = price2num($pu_ttc); - $pu_ht_devise = price2num($pu_ht_devise); - - if (!preg_match('/\((.*)\)/', $txtva)) { - $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5' - } + // Clean parameters + $fk_product = empty($fk_product) ? 0 : $fk_product; + $label = empty($label) ? '' : $label; + $remise_percent = empty($remise_percent) ? 0 : price2num($remise_percent); + $qty = price2num($qty); + $info_bits = empty($info_bits) ? 0 : $info_bits; + $pu_ht = price2num($pu_ht); + $pu_ttc = price2num($pu_ttc); + $pu_ht_devise = price2num($pu_ht_devise); + + if (!preg_match('/\((.*)\)/', $txtva)) { + $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5' + } - $txlocaltax1 = empty($txlocaltax1) ? 0 : price2num($txlocaltax1); - $txlocaltax2 = empty($txlocaltax2) ? 0 : price2num($txlocaltax2); - $this->multicurrency_total_ht = empty($this->multicurrency_total_ht) ? 0 : $this->multicurrency_total_ht; - $this->multicurrency_total_tva = empty($this->multicurrency_total_tva) ? 0 : $this->multicurrency_total_tva; - $this->multicurrency_total_ttc = empty($this->multicurrency_total_ttc) ? 0 : $this->multicurrency_total_ttc; + $txlocaltax1 = empty($txlocaltax1) ? 0 : price2num($txlocaltax1); + $txlocaltax2 = empty($txlocaltax2) ? 0 : price2num($txlocaltax2); + $this->multicurrency_total_ht = empty($this->multicurrency_total_ht) ? 0 : $this->multicurrency_total_ht; + $this->multicurrency_total_tva = empty($this->multicurrency_total_tva) ? 0 : $this->multicurrency_total_tva; + $this->multicurrency_total_ttc = empty($this->multicurrency_total_ttc) ? 0 : $this->multicurrency_total_ttc; - $pu = $price_base_type == 'HT' ? $pu_ht : $pu_ttc; + $pu = $price_base_type == 'HT' ? $pu_ht : $pu_ttc; - // Calculate total with, without tax and tax from qty, pu, remise_percent and txtva - // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker - // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva. + // Calculate total with, without tax and tax from qty, pu, remise_percent and txtva + // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker + // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva. - $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc); + $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc); - // Clean vat code - $vat_src_code = ''; - $reg = array(); - if (preg_match('/\((.*)\)/', $txtva, $reg)) { - $vat_src_code = $reg[1]; - $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate. - } + // Clean vat code + $vat_src_code = ''; + $reg = array(); + if (preg_match('/\((.*)\)/', $txtva, $reg)) { + $vat_src_code = $reg[1]; + $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate. + } - $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise); - - $total_ht = $tabprice[0]; - $total_tva = $tabprice[1]; - $total_ttc = $tabprice[2]; - $total_localtax1 = $tabprice[9]; - $total_localtax2 = $tabprice[10]; - $pu_ht = $tabprice[3]; - $pu_tva = $tabprice[4]; - $pu_ttc = $tabprice[5]; - - // MultiCurrency - $multicurrency_total_ht = $tabprice[16]; - $multicurrency_total_tva = $tabprice[17]; - $multicurrency_total_ttc = $tabprice[18]; - $pu_ht_devise = $tabprice[19]; - - $product_type = $type; - if ($fk_product) { - $product = new Product($this->db); - $result = $product->fetch($fk_product); - $product_type = $product->type; - } + $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise); + + $total_ht = $tabprice[0]; + $total_tva = $tabprice[1]; + $total_ttc = $tabprice[2]; + $total_localtax1 = $tabprice[9]; + $total_localtax2 = $tabprice[10]; + $pu_ht = $tabprice[3]; + $pu_tva = $tabprice[4]; + $pu_ttc = $tabprice[5]; + + // MultiCurrency + $multicurrency_total_ht = $tabprice[16]; + $multicurrency_total_tva = $tabprice[17]; + $multicurrency_total_ttc = $tabprice[18]; + $pu_ht_devise = $tabprice[19]; + + $product_type = $type; + if ($fk_product) { + $product = new Product($this->db); + $result = $product->fetch($fk_product); + $product_type = $product->type; + } - $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture_fourn_det_rec SET'; - $sql .= ' fk_facture_fourn = ' . ((int) $facid); - $sql .= ', fk_product = ' . ($fk_product > 0 ? ((int) $fk_product) : 'null'); - $sql .= ", ref = '" . $this->db->escape($ref) . "'"; - $sql .= ", label = '" . $this->db->escape($label) . "'"; - $sql .= ", description = '" . $this->db->escape($desc) . "'"; - $sql .= ', pu_ht = ' . price2num($pu_ht); - $sql .= ', qty = ' . price2num($qty); - $sql .= ", remise_percent = '" . price2num($remise_percent) . "'"; - $sql .= ", vat_src_code = '" . $this->db->escape($vat_src_code) . "'"; - $sql .= ', tva_tx = ' . price2num($txtva); - $sql .= ', localtax1_tx = ' . (float) $txlocaltax1; - $sql .= ", localtax1_type = '" . $this->db->escape($localtaxes_type[0]) . "'"; - $sql .= ', localtax2_tx = ' . (float) $txlocaltax2; - $sql .= ", localtax2_type = '" . $this->db->escape($localtaxes_type[2]) . "'"; - $sql .= ", total_ht = '" . price2num($total_ht) . "'"; - $sql .= ", total_tva = '" . price2num($total_tva) . "'"; - $sql .= ", total_localtax1 = '" . price2num($total_localtax1) . "'"; - $sql .= ", total_localtax2 = '" . price2num($total_localtax2) . "'"; - $sql .= ", total_ttc = '" . price2num($total_ttc) . "'"; - $sql .= ', product_type = ' . (int) $product_type; - $sql .= ', date_start = ' . (empty($date_start) ? 'NULL' : (int) $date_start); - $sql .= ', date_end = ' . (empty($date_end) ? 'NULL' : (int) $date_end); - $sql .= ', info_bits = ' . (int) $info_bits; - $sql .= ', special_code = ' . (int) $special_code; - $sql .= ', rang = ' . (int) $rang; - $sql .= ', fk_unit = ' . ($fk_unit ? "'" . $this->db->escape($fk_unit) . "'" : 'null'); - $sql .= ', fk_user_modif = ' . (int) $user; - $sql .= ', multicurrency_subprice = '.price2num($pu_ht_devise); - $sql .= ', multicurrency_total_ht = '.price2num($multicurrency_total_ht); - $sql .= ', multicurrency_total_tva = '.price2num($multicurrency_total_tva); - $sql .= ', multicurrency_total_ttc = '.price2num($multicurrency_total_ttc); - $sql .= ' WHERE rowid = ' . (int) $rowid; - - dol_syslog(get_class($this). '::updateline', LOG_DEBUG); - if ($this->db->query($sql)) { - $this->id = $facid; - $this->update_price(); - return 1; - } else { - $this->error = $this->db->lasterror(); - return -1; - } + $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture_fourn_det_rec SET'; + $sql .= ' fk_facture_fourn = ' . ((int) $facid); + $sql .= ', fk_product = ' . ($fk_product > 0 ? ((int) $fk_product) : 'null'); + $sql .= ", ref = '" . $this->db->escape($ref) . "'"; + $sql .= ", label = '" . $this->db->escape($label) . "'"; + $sql .= ", description = '" . $this->db->escape($desc) . "'"; + $sql .= ', pu_ht = ' . price2num($pu_ht); + $sql .= ', qty = ' . price2num($qty); + $sql .= ", remise_percent = '" . price2num($remise_percent) . "'"; + $sql .= ", vat_src_code = '" . $this->db->escape($vat_src_code) . "'"; + $sql .= ', tva_tx = ' . price2num($txtva); + $sql .= ', localtax1_tx = ' . (float) $txlocaltax1; + $sql .= ", localtax1_type = '" . $this->db->escape($localtaxes_type[0]) . "'"; + $sql .= ', localtax2_tx = ' . (float) $txlocaltax2; + $sql .= ", localtax2_type = '" . $this->db->escape($localtaxes_type[2]) . "'"; + $sql .= ", total_ht = '" . price2num($total_ht) . "'"; + $sql .= ", total_tva = '" . price2num($total_tva) . "'"; + $sql .= ", total_localtax1 = '" . price2num($total_localtax1) . "'"; + $sql .= ", total_localtax2 = '" . price2num($total_localtax2) . "'"; + $sql .= ", total_ttc = '" . price2num($total_ttc) . "'"; + $sql .= ', product_type = ' . (int) $product_type; + $sql .= ', date_start = ' . (empty($date_start) ? 'NULL' : (int) $date_start); + $sql .= ', date_end = ' . (empty($date_end) ? 'NULL' : (int) $date_end); + $sql .= ', info_bits = ' . (int) $info_bits; + $sql .= ', special_code = ' . (int) $special_code; + $sql .= ', rang = ' . (int) $rang; + $sql .= ', fk_unit = ' . ($fk_unit ? "'" . $this->db->escape($fk_unit) . "'" : 'null'); + $sql .= ', fk_user_modif = ' . (int) $user; + $sql .= ', multicurrency_subprice = '.price2num($pu_ht_devise); + $sql .= ', multicurrency_total_ht = '.price2num($multicurrency_total_ht); + $sql .= ', multicurrency_total_tva = '.price2num($multicurrency_total_tva); + $sql .= ', multicurrency_total_ttc = '.price2num($multicurrency_total_ttc); + $sql .= ' WHERE rowid = ' . (int) $rowid; + + dol_syslog(get_class($this). '::updateline', LOG_DEBUG); + if ($this->db->query($sql)) { + $this->id = $facid; + $this->update_price(); + return 1; + } else { + $this->error = $this->db->lasterror(); + return -1; } - return 0; } From ad3304832dd95cef3ad2c33ec3135987c339c8cc Mon Sep 17 00:00:00 2001 From: Mathieu Moulin Date: Tue, 6 Jan 2026 15:32:59 +0100 Subject: [PATCH 060/172] FIX : remove stock correctly when reception is deleted (like 82e092f) --- htdocs/reception/class/reception.class.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/htdocs/reception/class/reception.class.php b/htdocs/reception/class/reception.class.php index 8e98cfe639e4a..6915dc1ded42e 100644 --- a/htdocs/reception/class/reception.class.php +++ b/htdocs/reception/class/reception.class.php @@ -12,6 +12,7 @@ * Copyright (C) 2016-2022 Ferran Marcet * Copyright (C) 2018 Quentin Vial-Gouteyron * Copyright (C) 2022-2023 Frédéric France + * Copyright (C) 2026 Mathieu Moulin * * 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 @@ -1033,7 +1034,10 @@ public function delete(User $user) $this->db->begin(); // Stock control - if (isModEnabled('stock') && $conf->global->STOCK_CALCULATE_ON_RECEPTION && $this->statut > 0) { + if (isModEnabled('stock') && + ((getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION') && $this->statut > self::STATUS_DRAFT) || + (getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION_CLOSE') && $this->statut == self::STATUS_CLOSED)) + ) { require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php"; $langs->load("agenda"); From 2dbc4c5e01eeb2321788133f8cc250a41753bb2f Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Wed, 7 Jan 2026 11:08:40 +0100 Subject: [PATCH 061/172] fix: if holiday type have bock if negative, it is ignored when balance is 0 (#36817) * fix: backport of holiday approval if block_if_negative * fix: backport of holiday approval if block_if_negative --- htdocs/holiday/card.php | 6 +++--- htdocs/holiday/class/holiday.class.php | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/htdocs/holiday/card.php b/htdocs/holiday/card.php index 7bd3026350465..fab3b802ae72d 100644 --- a/htdocs/holiday/card.php +++ b/htdocs/holiday/card.php @@ -687,13 +687,13 @@ if (!$error) { $db->commit(); - - header('Location: '.$_SERVER["PHP_SELF"].'?id='.$object->id); - exit; } else { $db->rollback(); $action = ''; } + + header('Location: '.$_SERVER["PHP_SELF"].'?id='.$object->id); + exit; } } diff --git a/htdocs/holiday/class/holiday.class.php b/htdocs/holiday/class/holiday.class.php index 1102ed53dc75f..8d817661d9b99 100644 --- a/htdocs/holiday/class/holiday.class.php +++ b/htdocs/holiday/class/holiday.class.php @@ -882,8 +882,9 @@ public function approve($user = null, $notrigger = 0) if ($checkBalance > 0) { $balance = $this->getCPforUser($this->fk_user, $this->fk_type); + $daysAsked = num_open_day($this->date_debut, $this->date_fin, 0, 1); - if ($balance < 0) { + if (($balance - $daysAsked) < 0) { $this->error = 'LeaveRequestCreationBlockedBecauseBalanceIsNegative'; return -1; } From 05c11e71e2471e6ac16a4455e4cf1e5ddfba7c8f Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 7 Jan 2026 18:33:22 +0100 Subject: [PATCH 062/172] Fix CI --- .pre-commit-config.yaml | 2 +- .scrutinizer.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 82eb45c1d0bf9..74510a2119724 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -82,7 +82,7 @@ repos: pass_filenames: false # Run on all files - id: php-lint exclude: - (?x)^(htdocs/includes/symfony/var-dumper/Tests/.*)$ + (?x)^(htdocs/includes/symfony/var-dumper/Tests/.*)$ - id: php-stan stages: [manual] files: \.(php)$ diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 058a0ba8b4883..6a04407eadbae 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -128,7 +128,7 @@ tools: enabled: true # Disabled unused code. In most cases, we want to keep it. unused_code: - enabled: false + enabled: false deprecation_checks: enabled: true useless_function_calls: From d5cab67f4c669f6f3b214f56f7c55f901fba4585 Mon Sep 17 00:00:00 2001 From: Eric - CAP-REL <1468823+rycks@users.noreply.github.com> Date: Thu, 8 Jan 2026 09:55:28 +0100 Subject: [PATCH 063/172] Fix log message for empty cid_list apply same fix as 20.0 to avoid conflicts on next merges --- htdocs/core/class/CMailFile.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/class/CMailFile.class.php b/htdocs/core/class/CMailFile.class.php index 9e096d27f641c..337876482feca 100644 --- a/htdocs/core/class/CMailFile.class.php +++ b/htdocs/core/class/CMailFile.class.php @@ -319,7 +319,7 @@ public function __construct($subject, $to, $from, $msg, $filename_list = array() foreach ($filename_list as $i => $val) { if ($filename_list[$i]) { $this->atleastonefile = 1; - dol_syslog("CMailFile::CMailfile: filename_list[$i]=".$filename_list[$i].", mimetype_list[$i]=".$mimetype_list[$i]." mimefilename_list[$i]=".$mimefilename_list[$i]." cid_list[$i]=".(empty($cid_list[$i]) ? 'Not an embedded image' : $cid_list[$i]), LOG_DEBUG); + dol_syslog("CMailFile::CMailfile: filename_list[$i]=".$filename_list[$i].", mimetype_list[$i]=".$mimetype_list[$i]." mimefilename_list[$i]=".$mimefilename_list[$i]." cid_list[$i]=".(empty($cid_list[$i]) ? '' : $cid_list[$i]), LOG_DEBUG); } } } From f497a6a4fe3beea00ea3671d99a8e1fa471fa65b Mon Sep 17 00:00:00 2001 From: lvessiller-opendsi Date: Thu, 8 Jan 2026 18:54:15 +0100 Subject: [PATCH 064/172] FIX warning accountancy export from external module (#36832) --- htdocs/accountancy/class/accountancyexport.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/accountancy/class/accountancyexport.class.php b/htdocs/accountancy/class/accountancyexport.class.php index 472f5b5ce38ee..3ed4c1c758384 100644 --- a/htdocs/accountancy/class/accountancyexport.class.php +++ b/htdocs/accountancy/class/accountancyexport.class.php @@ -181,7 +181,7 @@ public static function getFormatCode($type) ); global $hookmanager; - $code = $formatcode[$type]; + $code = $formatcode[$type] ?? ''; $parameters = array('type' => $type); $reshook = $hookmanager->executeHooks('getFormatCode', $parameters, $code); From a5b106fe70e90c7b229257bfd7d932c80a8a620c Mon Sep 17 00:00:00 2001 From: Mathieu Moulin Date: Sat, 10 Jan 2026 17:30:45 +0100 Subject: [PATCH 065/172] Fix error removing reception with product batches (eatby/sellby) (#36846) --- htdocs/reception/class/reception.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/reception/class/reception.class.php b/htdocs/reception/class/reception.class.php index 6915dc1ded42e..9b2870eb7695c 100644 --- a/htdocs/reception/class/reception.class.php +++ b/htdocs/reception/class/reception.class.php @@ -1061,7 +1061,7 @@ public function delete(User $user) // we do not log origin because it will be deleted $mouvS->origin = null; - $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ReceptionDeletedInDolibarr", $this->ref), '', $obj->eatby, $obj->sellby, $obj->batch); // Price is set to 0, because we don't want to see WAP changed + $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ReceptionDeletedInDolibarr", $this->ref), '', $obj->eatby ? $this->db->jdate($obj->eatby) : null, $obj->sellby ? $this->db->jdate($obj->sellby) : null, $obj->batch); // Price is set to 0, because we don't want to see WAP changed if ($result < 0) { $error++; $this->error = $mouvS->error; From b3669d4333187b626b294cc4b1422feafe4bfcbf Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sat, 10 Jan 2026 17:45:20 +0100 Subject: [PATCH 066/172] Try to fix script --- .github/workflows/pr-18.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-18.yaml b/.github/workflows/pr-18.yaml index d12d700284482..733a3e788dce1 100644 --- a/.github/workflows/pr-18.yaml +++ b/.github/workflows/pr-18.yaml @@ -42,9 +42,11 @@ jobs: env: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} url: ${{ github.event.pull_request.html_url }} + url2: ${{ github.event.pull_request_target.html_url }} run: | echo "env.url=${{env.url}}" - gh pr edit "${{env.url}}" --add-label "Issue for v18 maintenance Team" + echo "env.url2=${{env.url2}}" + gh pr edit "${{env.url}}" --add-label "Issue for v18 maintenance Team" - name: Set reviewers except PR author id: set-reviewers @@ -58,6 +60,7 @@ jobs: FINAL_REVIEWERS+=("$reviewer") fi done + echo "AUTHOR=$AUTHOR" echo "reviewers=$(IFS=, ; echo "${FINAL_REVIEWERS[*]}")" >> $GITHUB_OUTPUT - name: Assign reviewers From 7b6051a0d0869990e4ad0e473d26bc37b622b45c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Fali=C3=A8re?= <121813548+BenjaminFlr@users.noreply.github.com> Date: Sat, 10 Jan 2026 17:46:45 +0100 Subject: [PATCH 067/172] FIX(ticket): check on TICKET_IMAGE_PUBLIC_INTERFACE (#36833) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FIX(ticket): check on TICKET_IMAGE_PUBLIC_INTERFACE Must use getDolGlobalString instead of getDolGlobalInt here * Add contributor --------- Co-authored-by: Benjamin Falière --- htdocs/core/lib/ticket.lib.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/htdocs/core/lib/ticket.lib.php b/htdocs/core/lib/ticket.lib.php index fd777fbac2b1c..e46da88f5b58d 100644 --- a/htdocs/core/lib/ticket.lib.php +++ b/htdocs/core/lib/ticket.lib.php @@ -2,6 +2,7 @@ /* Copyright (C) 2013-2018 Jean-François FERRY * Copyright (C) 2016 Christophe Battarel * Copyright (C) 2019-2022 Frédéric France + * Copyright (C) 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 @@ -261,7 +262,7 @@ function llxHeaderTicket($title, $head = "", $disablejs = 0, $disablehead = 0, $ print ''; } - if (getDolGlobalInt('TICKET_IMAGE_PUBLIC_INTERFACE')) { + if (getDolGlobalString('TICKET_IMAGE_PUBLIC_INTERFACE')) { print '
'; print ''; print '
'; From 2e7ab21d017e72d5d3812c018426db818e571039 Mon Sep 17 00:00:00 2001 From: ThomasNgr-OpenDSI Date: Tue, 13 Jan 2026 11:37:36 +0100 Subject: [PATCH 068/172] FIX #GHSA-w5j3-8fcr-h87w (#36868) Co-authored-by: Laurent Destailleur --- htdocs/includes/odtphp/odf.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/htdocs/includes/odtphp/odf.php b/htdocs/includes/odtphp/odf.php index 39f595f493443..4fd2956218347 100644 --- a/htdocs/includes/odtphp/odf.php +++ b/htdocs/includes/odtphp/odf.php @@ -833,7 +833,7 @@ public function exportAsAttachedPDF($name = "") // using windows libreoffice that must be in path // using linux/mac libreoffice that must be in path // Note PHP Config "fastcgi.impersonate=0" must set to 0 - Default is 1 - $command ='soffice --headless -env:UserInstallation=file:\''.$conf->user->dir_temp.'/odtaspdf\' --convert-to pdf --outdir '. escapeshellarg(dirname($name)). " ".escapeshellarg($name); + $command ='soffice --headless -env:UserInstallation=file:\''.dol_sanitizePathName($conf->user->dir_temp).'/odtaspdf\' --convert-to pdf --outdir '. escapeshellarg(dirname($name)). " ".escapeshellarg($name); } elseif (preg_match('/unoconv/', getDolGlobalString('MAIN_ODT_AS_PDF'))) { // If issue with unoconv, see https://github.com/dagwieers/unoconv/issues/87 @@ -859,17 +859,19 @@ public function exportAsAttachedPDF($name = "") // - set shell of user to bash instead of nologin. // - set permission to read/write to user on home directory /var/www so user can create the libreoffice , dconf and .cache dir and files then set permission back - $command = getDolGlobalString('MAIN_ODT_AS_PDF').' '.escapeshellcmd($name); + $command = getDolGlobalString('MAIN_ODT_AS_PDF').' '.escapeshellarg($name); //$command = '/usr/bin/unoconv -vvv '.escapeshellcmd($name); } else { // deprecated old method using odt2pdf.sh (native, jodconverter, ...) $tmpname=preg_replace('/\.odt/i', '', $name); if (getDolGlobalString('MAIN_DOL_SCRIPTS_ROOT')) { - $command = getDolGlobalString('MAIN_DOL_SCRIPTS_ROOT').'/scripts/odt2pdf/odt2pdf.sh '.escapeshellcmd($tmpname).' '.(is_numeric(getDolGlobalString('MAIN_ODT_AS_PDF'))?'jodconverter':getDolGlobalString('MAIN_ODT_AS_PDF')); + $command = dol_sanitizePathName(getDolGlobalString('MAIN_DOL_SCRIPTS_ROOT')).'/scripts/odt2pdf/odt2pdf.sh '.escapeshellarg($tmpname).' '.escapeshellarg(is_numeric(getDolGlobalString('MAIN_ODT_AS_PDF')) ? 'jodconverter' : getDolGlobalString('MAIN_ODT_AS_PDF')); } else { dol_syslog(get_class($this).'::exportAsAttachedPDF is used but the constant MAIN_DOL_SCRIPTS_ROOT with path to script directory was not defined.', LOG_WARNING); - $command = '../../scripts/odt2pdf/odt2pdf.sh '.escapeshellcmd($tmpname).' '.(is_numeric(getDolGlobalString('MAIN_ODT_AS_PDF'))?'jodconverter':getDolGlobalString('MAIN_ODT_AS_PDF')); + $paramodt2pdf = (is_numeric(getDolGlobalString('MAIN_ODT_AS_PDF')) ? 'jodconverter' : getDolGlobalString('MAIN_ODT_AS_PDF')); + $paramodt2pdf = dol_sanitizePathName($paramodt2pdf); + $command = '../../scripts/odt2pdf/odt2pdf.sh '.escapeshellarg($tmpname).' '.escapeshellarg($paramodt2pdf); } } @@ -877,6 +879,7 @@ public function exportAsAttachedPDF($name = "") //$command = DOL_DOCUMENT_ROOT.'/includes/odtphp/odt2pdf.sh '.$name.' '.$dirname; dol_syslog(get_class($this).'::exportAsAttachedPDF $execmethod='.$execmethod.' Run command='.$command, LOG_DEBUG); + // TODO Use: // $outputfile = DOL_DATA_ROOT.'/odt2pdf.log'; // $result = $utils->executeCLI($command, $outputfile); and replace test on $execmethod. @@ -884,17 +887,17 @@ public function exportAsAttachedPDF($name = "") // $errorstring will be $result['output'] $retval=0; $output_arr=array(); if ($execmethod == 1) { - exec($command, $output_arr, $retval); + exec(escapeshellcmd($command), $output_arr, $retval); } if ($execmethod == 2) { $outputfile = DOL_DATA_ROOT.'/odt2pdf.log'; - $ok=0; $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); fwrite($handle, $command."\n"); - $handlein = popen($command, 'r'); + $handlein = popen(escapeshellcmd($command), 'r'); while (!feof($handlein)) { $read = fgets($handlein); fwrite($handle, $read); @@ -937,7 +940,7 @@ public function exportAsAttachedPDF($name = "") foreach ($output_arr as $line) { $errorstring.= $line."
"; } - throw new OdfException('ODT to PDF convert fail (option MAIN_ODT_AS_PDF is '.$conf->global->MAIN_ODT_AS_PDF.', command was '.$command.', retval='.$retval.') : ' . $errorstring); + throw new OdfException('ODT to PDF convert fail (option MAIN_ODT_AS_PDF is '.getDolGlobalString('MAIN_ODT_AS_PDF').', command was '.$command.', retval='.$retval.') : ' . $errorstring); } } } From 2cfdbd6b5ff2c29d632eeff79a18c44e29ccd230 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 13 Jan 2026 16:04:20 +0100 Subject: [PATCH 069/172] Try to use PR ID instead of PR url --- .github/workflows/pr-18.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-18.yaml b/.github/workflows/pr-18.yaml index 733a3e788dce1..ef651ffa8e609 100644 --- a/.github/workflows/pr-18.yaml +++ b/.github/workflows/pr-18.yaml @@ -41,12 +41,14 @@ jobs: - name: Assign Tag env: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} + prid: ${{ github.event.pull_request.number }} url: ${{ github.event.pull_request.html_url }} url2: ${{ github.event.pull_request_target.html_url }} run: | + echo "env.prid=${{env.prid}}" echo "env.url=${{env.url}}" echo "env.url2=${{env.url2}}" - gh pr edit "${{env.url}}" --add-label "Issue for v18 maintenance Team" + gh pr edit "${{env.prid}}" --add-label "Issue for v18 maintenance Team" - name: Set reviewers except PR author id: set-reviewers @@ -67,8 +69,9 @@ jobs: if: steps.set-reviewers.outputs.reviewers != '' env: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} + prid: ${{ github.event.pull_request.number }} url: ${{ github.event.pull_request.html_url }} reviewers: ${{ steps.set-reviewers.outputs.reviewers }} run: | echo "Assigning reviewers: ${{env.reviewers}}" - gh pr edit "${{env.url}}" --add-reviewer "${{env.reviewers}}" + gh pr edit "${{env.prid}}" --add-reviewer "${{env.reviewers}}" From 39d30a23fe3c7b8cf8ffc5619b3654fba2425830 Mon Sep 17 00:00:00 2001 From: atm-lucas <121817516+atm-lucasmantegari@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:44:09 +0100 Subject: [PATCH 070/172] Duplicates in the result of the SQL query (#36913) --- htdocs/compta/resultat/clientfourn.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/htdocs/compta/resultat/clientfourn.php b/htdocs/compta/resultat/clientfourn.php index cf2a26979a949..197d0a7bfc592 100644 --- a/htdocs/compta/resultat/clientfourn.php +++ b/htdocs/compta/resultat/clientfourn.php @@ -292,16 +292,19 @@ if ($showaccountdetail == 'no') { $sql .= ", f.thirdparty_code as name"; } - $sql .= " FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as f"; - $sql .= ", ".MAIN_DB_PREFIX."accounting_account as aa"; - $sql .= " WHERE f.numero_compte = aa.account_number"; + $sql .= " FROM ".$db->prefix()."accounting_bookkeeping as f"; + $sql .= " INNER JOIN ".$db->prefix()."accounting_account as aa"; + $sql .= " ON aa.account_number = f.numero_compte"; + $sql .= " AND aa.entity = f.entity"; // Security prevents duplicate. + $sql .= " WHERE 1=1"; $sql .= " AND ".$predefinedgroupwhere; - $sql .= " AND fk_pcg_version = '".$db->escape($charofaccountstring)."'"; + $sql .= " AND aa.fk_pcg_version = '".$db->escape($charofaccountstring)."'"; $sql .= " AND f.entity = ".$conf->entity; if (!empty($date_start) && !empty($date_end)) { - $sql .= " AND f.doc_date >= '".$db->idate($date_start)."' AND f.doc_date <= '".$db->idate($date_end)."'"; + $sql .= " AND f.doc_date >= '".$db->idate($date_start)."'"; + $sql .= " AND f.doc_date <= '".$db->idate($date_end)."'"; } - $sql .= " GROUP BY pcg_type"; + $sql .= " GROUP BY aa.pcg_type"; if ($showaccountdetail == 'no') { $sql .= ", name, socid"; // group by "accounting group" (INCOME/EXPENSE), then "customer". } From 926ed04ebe3b9fd152dde6584b1dcd6366eb920e Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Tue, 20 Jan 2026 13:58:17 +0100 Subject: [PATCH 071/172] fix: PHP fatal error on php 8.2 Unsupported operand types: string * string (#36930) --- htdocs/mrp/mo_production.php | 2 +- htdocs/product/stock/class/mouvementstock.class.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/htdocs/mrp/mo_production.php b/htdocs/mrp/mo_production.php index 4a48a1fbaabde..b883cd4831600 100644 --- a/htdocs/mrp/mo_production.php +++ b/htdocs/mrp/mo_production.php @@ -250,7 +250,7 @@ $i = 1; while (GETPOSTISSET('qty-'.$line->id.'-'.$i)) { - $qtytoprocess = price2num(GETPOST('qty-'.$line->id.'-'.$i)); + $qtytoprocess = (float) price2num(GETPOST('qty-'.$line->id.'-'.$i)); if ($qtytoprocess != 0) { // Check warehouse is set if we should have to diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index 9506db38c4a32..a6ed0bc0d1542 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -505,6 +505,7 @@ public function _create($user, $fk_product, $entrepot_id, $qty, $type, $price = // Test if there is already a record for couple (warehouse / product), so later we will make an update or create. $alreadyarecord = 0; + $fk_product_stock = 0; if (!$error) { $sql = "SELECT rowid, reel FROM ".$this->db->prefix()."product_stock"; $sql .= " WHERE fk_entrepot = ".((int) $entrepot_id)." AND fk_product = ".((int) $fk_product); // This is a unique key @@ -535,7 +536,7 @@ public function _create($user, $fk_product, $entrepot_id, $qty, $type, $price = if ($price > 0 || (getDolGlobalString('STOCK_UPDATE_AWP_EVEN_WHEN_ENTRY_PRICE_IS_NULL') && $price == 0 && in_array($this->origin_type, array('order_supplier', 'invoice_supplier')))) { $oldqtytouse = ($oldqty >= 0 ? $oldqty : 0); // We make a test on oldpmp>0 to avoid to use normal rule on old data with no pmp field defined - if ($oldpmp > 0) { + if ($oldpmp > 0 && ($oldqtytouse + $qty) != 0) { $newpmp = price2num((($oldqtytouse * $oldpmp) + ($qty * $price)) / ($oldqtytouse + $qty), 'MU'); } else { $newpmp = $price; // For this product, PMP was not yet set. We set it to input price. From 36b681153e3b044c382e8fd97338a825584bc330 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 23 Jan 2026 17:14:17 +0100 Subject: [PATCH 072/172] Fix CI --- .github/workflows/phan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/phan.yml b/.github/workflows/phan.yml index e2ce5a7544393..7a8aa546e1a33 100644 --- a/.github/workflows/phan.yml +++ b/.github/workflows/phan.yml @@ -37,7 +37,7 @@ jobs: with: php-version: 8.2 coverage: none # disable xdebug, pcov - tools: cs2pr,phan + tools: cs2pr,phan:5.5.2 - name: Run Phan analysis run: | phan $PHAN_QUICK -k $PHAN_CONFIG -B $PHAN_BASELINE --analyze-twice --minimum-target-php-version $PHAN_MIN_PHP --output-mode=checkstyle -o _phan.xml From c19e4e33cbe2f079dc12b42ac6c438d05d39b89a Mon Sep 17 00:00:00 2001 From: noec764 <58433943+noec764@users.noreply.github.com> Date: Fri, 23 Jan 2026 18:29:12 +0100 Subject: [PATCH 073/172] FIX: Massaction Create Bills from expedition (#36958) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Noé Co-authored-by: Laurent Destailleur --- htdocs/expedition/list.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/expedition/list.php b/htdocs/expedition/list.php index 4527c8189c5e8..82895dec5194c 100644 --- a/htdocs/expedition/list.php +++ b/htdocs/expedition/list.php @@ -299,7 +299,7 @@ } if ($objecttmp->id > 0) { - $res = $objecttmp->add_object_linked($objecttmp->origin, $id_sending); + $res = $objecttmp->add_object_linked($objecttmp->origin_type, $id_sending); if ($res == 0) { $errors[] = $expd->ref.' : '.$langs->trans($objecttmp->errors[0]); From ee32411d6a00b39d006a7102d31d1d990788e7de Mon Sep 17 00:00:00 2001 From: Alexandre SPANGARO Date: Sat, 24 Jan 2026 00:00:14 +0100 Subject: [PATCH 074/172] FIX Contract - Wrong button to edit contract odt path (#36965) Co-authored-by: Laurent Destailleur --- .../modules/contract/doc/doc_generic_contract_odt.modules.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/modules/contract/doc/doc_generic_contract_odt.modules.php b/htdocs/core/modules/contract/doc/doc_generic_contract_odt.modules.php index 92882228e1792..2678a9777a543 100644 --- a/htdocs/core/modules/contract/doc/doc_generic_contract_odt.modules.php +++ b/htdocs/core/modules/contract/doc/doc_generic_contract_odt.modules.php @@ -145,7 +145,7 @@ public function info($langs) $texte .= getDolGlobalString('CONTRACT_ADDON_PDF_ODT_PATH'); $texte .= ''; $texte .= '
'; - $texte .= ''; + $texte .= ''; $texte .= '
'; // Scan directories @@ -153,7 +153,7 @@ public function info($langs) if (getDolGlobalString('CONTRACT_ADDON_PDF_ODT_PATH')) { $texte .= $langs->trans("NumberOfModelFilesFound").': '; //$texte.=$nbofiles?'':''; - $texte .= count($listoffiles); + $texte .= $nbofiles; //$texte.=$nbofiles?'':''; $texte .= ''; } From e3323c3ff17a655b48928cfbbac8dd52e9c6a09d Mon Sep 17 00:00:00 2001 From: Alexandre SPANGARO Date: Mon, 26 Jan 2026 11:49:46 +0100 Subject: [PATCH 075/172] Remove message for Invoice Situation level 2 - Need more test (#36987) --- htdocs/accountancy/index.php | 2 ++ htdocs/index.php | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/htdocs/accountancy/index.php b/htdocs/accountancy/index.php index 0577cac80fab9..c10ca3155c1de 100644 --- a/htdocs/accountancy/index.php +++ b/htdocs/accountancy/index.php @@ -122,12 +122,14 @@ print load_fiche_titre($langs->trans("AccountancyArea"), empty($resultboxes['selectboxlist']) ? '' : $resultboxes['selectboxlist'], 'accountancy', 0, '', '', $showtutorial); + /* if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) { $messagewarning = $langs->trans("SorryThisModuleIsNotCompatibleWithTheExperimentalFeatureOfSituationInvoices"); $messagewarning .= ' '.$langs->trans("WarningExperimentalFeatureInvoiceSituationNeedToUpgradeToProgressiveMode", 'https://partners.dolibarr.org'); print info_admin($messagewarning); print "
"; } + */ if (!$helpisexpanded && empty($resultboxes['boxlista']) && empty($resultboxes['boxlistb'])) { print '

'.$langs->trans("ClickOnUseTutorialForHelp", $langs->transnoentities("ShowTutorial"))."
\n"; diff --git a/htdocs/index.php b/htdocs/index.php index 68fce7df62d09..b8a6861771468 100644 --- a/htdocs/index.php +++ b/htdocs/index.php @@ -118,11 +118,13 @@ */ // Specific warning to propose to upgrade invoice situation to progressive mode -if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1 && (float) DOL_VERSION >= 22.0) { +/* +if (getDolGlobalInt('INVOICE_USE_SITUATION') == 1) { $langs->loadLangs(array("admin")); print info_admin($langs->trans("WarningExperimentalFeatureInvoiceSituationNeedToUpgradeToProgressiveMode", 'https://partners.dolibarr.org')); //print "
"; } +*/ /* * Show security warnings From 1b2f16b01a311249058145f3568a512cf75a6159 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 27 Jan 2026 10:42:37 +0100 Subject: [PATCH 076/172] Fix ci --- htdocs/core/modules/modProduct.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/modules/modProduct.class.php b/htdocs/core/modules/modProduct.class.php index 36dae6c27f758..092305cdbf11e 100644 --- a/htdocs/core/modules/modProduct.class.php +++ b/htdocs/core/modules/modProduct.class.php @@ -879,8 +879,8 @@ public function __construct($db) $this->import_regex_array[$r] = array( 'pr.datec'=>'^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$', 'pr.recuperableonly'=>'^[0|1]$' - ); - $this->import_convertvalue_array[$r] = array( + ); + $this->import_convertvalue_array[$r] = array( 'pr.fk_product'=>array('rule'=>'fetchidfromref', 'classfile'=>'/product/class/product.class.php', 'class'=>'Product', 'method'=>'fetch', 'element'=>'Product') ); $this->import_examplevalues_array[$r] = array('pr.fk_product'=>"ref:PRODUCT_REF or id:123456", From 51e61c8ef3e8a470c1570708512321eeded5f3c6 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Wed, 28 Jan 2026 15:17:39 +0100 Subject: [PATCH 077/172] FIX: missing code to add Ressource to event on creation card (#37002) --- htdocs/comm/action/card.php | 90 ++++++++++++++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 5 deletions(-) diff --git a/htdocs/comm/action/card.php b/htdocs/comm/action/card.php index 3076f3d4854b4..40b6f99bf04c3 100644 --- a/htdocs/comm/action/card.php +++ b/htdocs/comm/action/card.php @@ -410,6 +410,11 @@ if (!empty($_SESSION['assignedtouser'])) { $listofuserid = json_decode($_SESSION['assignedtouser'], true); } + + if (!empty($_SESSION['assignedtoresource'])) { + $listofresourceid = json_decode($_SESSION['assignedtoresource'], true); + } + $i = 0; foreach ($listofuserid as $key => $value) { if ($i == 0) { // First entry @@ -520,22 +525,95 @@ // Creation of action/event $idaction = $object->create($user); + $moreparam = ''; if ($idaction > 0) { if (!$object->error) { + if (is_array($listofresourceid) && count($listofresourceid)) { + foreach ($listofresourceid as $resource_id => $val) { + $resource_type = 'dolresource'; + $busy = 1;//GETPOSTINT('busy'); + + // Resources association + if (getDolGlobalString('RESOURCE_USED_IN_EVENT_CHECK')) { + $eventDateStart = $object->datep; + $eventDateEnd = $object->datef; + $isFullDayEvent = $object->fulldayevent; + if (empty($eventDateEnd)) { + if ($isFullDayEvent) { + $eventDateStartArr = dol_getdate($eventDateStart); + $eventDateStart = dol_mktime(0, 0, 0, $eventDateStartArr['mon'], $eventDateStartArr['mday'], $eventDateStartArr['year']); + $eventDateEnd = dol_mktime(23, 59, 59, $eventDateStartArr['mon'], $eventDateStartArr['mday'], $eventDateStartArr['year']); + } + } + + $sql = "SELECT er.rowid, r.ref as r_ref, ac.id as ac_id, ac.label as ac_label"; + $sql .= " FROM " . MAIN_DB_PREFIX . "element_resources as er"; + $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "resource as r ON r.rowid = er.resource_id AND er.resource_type = '" . $db->escape($resource_type) . "'"; + $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "actioncomm as ac ON ac.id = er.element_id AND er.element_type = '" . $db->escape($object->element) . "'"; + $sql .= " WHERE er.resource_id = " . ((int) $resource_id); + $sql .= " AND er.busy = 1"; + $sql .= " AND ("; + + // event date start between ac.datep and ac.datep2 (if datep2 is null we consider there is no end) + $sql .= " (ac.datep <= '" . $db->idate($eventDateStart) . "' AND (ac.datep2 IS NULL OR ac.datep2 >= '" . $db->idate($eventDateStart) . "'))"; + // event date end between ac.datep and ac.datep2 + if (!empty($eventDateEnd)) { + $sql .= " OR (ac.datep <= '" . $db->idate($eventDateEnd) . "' AND (ac.datep2 >= '" . $db->idate($eventDateEnd) . "'))"; + } + // event date start before ac.datep and event date end after ac.datep2 + $sql .= " OR ("; + $sql .= "ac.datep >= '" . $db->idate($eventDateStart) . "'"; + if (!empty($eventDateEnd)) { + $sql .= " AND (ac.datep2 IS NOT NULL AND ac.datep2 <= '" . $db->idate($eventDateEnd) . "')"; + } + $sql .= ")"; + + $sql .= ")"; + $resql = $db->query($sql); + if (!$resql) { + $error++; + $object->error = $db->lasterror(); + $object->errors[] = $object->error; + } else { + if ($db->num_rows($resql) > 0) { + // Resource already in use + $error++; + $object->error = $langs->trans('ErrorResourcesAlreadyInUse') . ' : '; + while ($obj = $db->fetch_object($resql)) { + $object->error .= '
- ' . $langs->trans('ErrorResourceUseInEvent', $obj->r_ref, $obj->ac_label . ' [' . $obj->ac_id . ']'); + } + $object->errors[] = $object->error; + + setEventMessages($object->error, null, 'errors'); + } + $db->free($resql); + } + } + + if (!$error) { + $res = $object->add_element_resource($resource_id, $resource_type, $busy, $val['mandatory']); + } + } + } + + unset($_SESSION['assignedtoresource']); + + // Category association - $categories = GETPOST('categories', 'array'); - $object->setCategories($categories); + if (!$error) { + $categories = GETPOST('categories', 'array'); + $object->setCategories($categories); + } unset($_SESSION['assignedtouser']); - $moreparam = ''; if ($user->id != $object->userownerid) { $moreparam = "filtert=-1"; // We force to remove filter so created record is visible when going back to per user view. } // Create reminders - if ($addreminder == 'on') { + if (!$error && $addreminder == 'on') { $actionCommReminder = new ActionCommReminder($db); $dateremind = dol_time_plus_duree($datep, -1 * $offsetvalue, $offsetunit); @@ -606,7 +684,9 @@ $donotclearsession = 1; } - if ($eventisrecurring) { + if (!$error && $eventisrecurring) { + $dayoffset = 0; + $monthoffset = 0; // We set first date of recurrence and offsets if ($selectedrecurrulefreq == 'WEEKLY' && !empty($selectedrecurrulebyday)) { $firstdatearray = dol_get_first_day_week(GETPOST("apday", 'int'), GETPOST("apmonth", 'int'), GETPOST("apyear", 'int')); From 07ff579416ad1a9d2287e00c6af45de495ffba7a Mon Sep 17 00:00:00 2001 From: Eric Seigne Date: Thu, 29 Jan 2026 10:01:29 +0100 Subject: [PATCH 078/172] auto assign PR to 18 thanks to copilot --- .github/workflows/pr-18.yaml | 178 +++++++++++++++++++++++++---------- 1 file changed, 127 insertions(+), 51 deletions(-) diff --git a/.github/workflows/pr-18.yaml b/.github/workflows/pr-18.yaml index ef651ffa8e609..379657c529358 100644 --- a/.github/workflows/pr-18.yaml +++ b/.github/workflows/pr-18.yaml @@ -1,11 +1,17 @@ -# Action to prepare the github action -# Go on Dolibarr - Settings - Developer settings - Enter a name + webhook to disable + Permissions (see app test) + Can install by any account -# Click on generate the private keys -# Click on Install application - choose user of the Organization -# Go on Organisation - Secret and variables and create a secret PR18_SECRET_KEY and copy the content of received private key. Choose the repository access to "Repository Dolibarr". -# Go on Organisation - Secret and variables and create a variable PR18_APP_ID and copy the ID of the previously create ID. Choose the repository access to "Repository Dolibarr". +# Action to prepare the GitHub Action +# Prerequisites (create in the organization / repo): +# - Secret: PR18_SECRET_KEY (private key generated for the GitHub App) +# - Variable: PR18_APP_ID (GitHub App ID) +# +# Behavior: +# - On pull_request_target (opened, synchronize, reopened) against branch 18.0: +# - Generate a GitHub App token +# - Add the label "Issue for v18 maintenance Team" to the PR (error-tolerant) +# - Assign the reviewers listed below, excluding the PR author +# -> attempt per reviewer (if a reviewer fails, log and continue) +# -> the step fails only if no reviewer could be added +# - On push to 18.0: workflow runs but PR-specific actions are skipped # - name: Set reviewer and label for v18 on: pull_request_target: @@ -24,54 +30,124 @@ jobs: assign-and-label-v18: runs-on: ubuntu-latest + # Mergeers / reviewers list: edit here as needed (comma-separated) + env: + REVIEWERS: "lvessiller-opendsi,rycks" + # Label name to apply + V18_LABEL: "Issue for v18 maintenance Team" + steps: - #- name: Install GitHub CLI - # run: sudo apt-get install gh + # 1) Generate a GitHub App token (via actions/create-github-app-token) + - name: Generate GitHub App token + id: generate-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.PR18_APP_ID }} + private-key: ${{ secrets.PR18_SECRET_KEY }} + + # 2) Checkout repository (useful if repo content is needed later) + - name: Checkout repository + uses: actions/checkout@v4 - - name: Generate a token - id: generate-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ vars.PR18_APP_ID }} - private-key: ${{ secrets.PR18_SECRET_KEY }} + # 3) Install / configure GitHub CLI (gh) on the runner + - name: Setup GitHub CLI (gh) + uses: cli/gh-action@v3 - - name: Checkout repository - uses: actions/checkout@v4 + # Debug information (useful for diagnostics) + - name: Debug info + run: | + echo "Event: $GITHUB_EVENT_NAME" + echo "Ref: $GITHUB_REF" + echo "Run id: $GITHUB_RUN_ID" + echo "Reviewers configured: $REVIEWERS" - - name: Assign Tag - env: - GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} - prid: ${{ github.event.pull_request.number }} - url: ${{ github.event.pull_request.html_url }} - url2: ${{ github.event.pull_request_target.html_url }} - run: | - echo "env.prid=${{env.prid}}" - echo "env.url=${{env.url}}" - echo "env.url2=${{env.url2}}" - gh pr edit "${{env.prid}}" --add-label "Issue for v18 maintenance Team" + # 4) Add the label to the PR (PR events only) + # -> tolerant to errors: log on failure but do not fail the job + - name: Add label to PR (pull_request events only) + if: ${{ github.event_name == 'pull_request' }} + env: + GH_TOKEN: ${{ steps.generate-token.outputs.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + V18_LABEL: ${{ env.V18_LABEL }} + run: | + set -euo pipefail + echo "Adding label '$V18_LABEL' to PR #${PR_NUMBER}" + if gh pr edit "$PR_NUMBER" --add-label "$V18_LABEL"; then + echo "Label added successfully." + else + echo "Warning: failed to add label '$V18_LABEL' to PR #${PR_NUMBER}. Continuing." + fi + + # 5) Compute final reviewers list excluding the PR author + - name: Compute reviewers (exclude PR author) + if: ${{ github.event_name == 'pull_request' }} + id: set-reviewers + run: | + set -euo pipefail + IFS=',' read -ra ALL_REVIEWERS <<< "${REVIEWERS}" + AUTHOR="${{ github.event.pull_request.user.login }}" + FINAL=() + for r in "${ALL_REVIEWERS[@]}"; do + r_trimmed="$(echo "$r" | xargs)" + if [ -z "$r_trimmed" ]; then + continue + fi + if [ "$r_trimmed" != "$AUTHOR" ]; then + FINAL+=("$r_trimmed") + fi + done + if [ ${#FINAL[@]} -eq 0 ]; then + echo "reviewers=" >> $GITHUB_OUTPUT + else + reviewers_csv="$(IFS=, ; echo "${FINAL[*]}")" + echo "reviewers=${reviewers_csv}" >> $GITHUB_OUTPUT + fi + echo "author=$AUTHOR" >> $GITHUB_OUTPUT + echo "Computed reviewers: ${reviewers_csv:-}" - - name: Set reviewers except PR author - id: set-reviewers - run: | - # Liste des reviewers à ajuster selon équipe - REVIEWERS=("lvessiller-opendsi" "rycks") - AUTHOR="${{ github.event.pull_request.user.login }}" - FINAL_REVIEWERS=() - for reviewer in "${REVIEWERS[@]}"; do - if [ "$reviewer" != "$AUTHOR" ]; then - FINAL_REVIEWERS+=("$reviewer") + # 6) Assign reviewers one-by-one with fine-grained error handling + # - try each reviewer, track successes and failures + # - fail the step only if none could be added + # - succeed if at least one was added (but log failures) + - name: Assign reviewers on PR (per-reviewer, tolerant errors) + if: ${{ github.event_name == 'pull_request' && steps.set-reviewers.outputs.reviewers != '' }} + env: + GH_TOKEN: ${{ steps.generate-token.outputs.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REVIEWERS_CSV: ${{ steps.set-reviewers.outputs.reviewers }} + run: | + # Note: avoid 'set -e' to handle per-reviewer failures manually + set -uo pipefail + IFS=',' read -ra TO_ADD <<< "${REVIEWERS_CSV}" + SUCCESS=0 + FAILED=() + for r in "${TO_ADD[@]}"; do + r_trimmed="$(echo "$r" | xargs)" + if [ -z "$r_trimmed" ]; then + continue + fi + echo "Attempting to add reviewer: $r_trimmed" + if gh pr edit "$PR_NUMBER" --add-reviewer "$r_trimmed"; then + echo "Added reviewer: $r_trimmed" + SUCCESS=$((SUCCESS+1)) + else + echo "Warning: failed to add reviewer: $r_trimmed" + echo "Debug: PR view:" + gh pr view "$PR_NUMBER" --json number,title,author,reviewRequests || true + FAILED+=("$r_trimmed") + fi + done + if [ $SUCCESS -eq 0 ]; then + echo "Error: none of the configured reviewers could be added: ${FAILED[*]:-}" + # Fail the step because no reviewer was added + exit 1 + else + echo "Reviewers added: ${SUCCESS}. Failed to add: ${FAILED[*]:-none}" + # Step succeeds even if some reviewers failed fi - done - echo "AUTHOR=$AUTHOR" - echo "reviewers=$(IFS=, ; echo "${FINAL_REVIEWERS[*]}")" >> $GITHUB_OUTPUT - - name: Assign reviewers - if: steps.set-reviewers.outputs.reviewers != '' - env: - GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} - prid: ${{ github.event.pull_request.number }} - url: ${{ github.event.pull_request.html_url }} - reviewers: ${{ steps.set-reviewers.outputs.reviewers }} - run: | - echo "Assigning reviewers: ${{env.reviewers}}" - gh pr edit "${{env.prid}}" --add-reviewer "${{env.reviewers}}" + # 7) Push event notice (no PR-specific actions performed) + - name: Push event notice + if: ${{ github.event_name == 'push' }} + run: | + echo "Triggered by push on branch 18.0. No PR-specific actions performed." From 70b3be5e7f40f21052745854f8eeb7e698e4db2b Mon Sep 17 00:00:00 2001 From: Sylvain Legrand Date: Thu, 29 Jan 2026 13:02:25 +0100 Subject: [PATCH 079/172] Fix from V22 (#35559) Using a packaging with a float value less than 1 (0.1, 0.5, etc.) generates a 500 error (division by 0) Co-authored-by: Laurent Destailleur Co-authored-by: Eric - CAP-REL <1468823+rycks@users.noreply.github.com> --- .../class/fournisseur.commande.class.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/htdocs/fourn/class/fournisseur.commande.class.php b/htdocs/fourn/class/fournisseur.commande.class.php index ee155e7ad3d15..4cfb71d908f52 100644 --- a/htdocs/fourn/class/fournisseur.commande.class.php +++ b/htdocs/fourn/class/fournisseur.commande.class.php @@ -616,7 +616,7 @@ public function fetch_lines($only_product = 0) $objsearchpackage = $this->db->fetch_object($resqlsearchpackage); if ($objsearchpackage) { $line->fk_fournprice = $objsearchpackage->rowid; - $line->packaging = $objsearchpackage->packaging; + $line->packaging = (float) $objsearchpackage->packaging; } } else { $this->error = $this->db->lasterror(); @@ -1979,16 +1979,16 @@ public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0.0, $txloca } // Predefine quantity according to packaging - if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) { + if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) { $prod = new Product($this->db); - $prod->get_buyprice($fk_prod_fourn_price, $qty, $fk_product, 'none', (empty($this->fk_soc) ? $this->socid : $this->fk_soc)); + $prod->get_buyprice($fk_prod_fourn_price, (float) $qty, $fk_product, 'none', (empty($this->fk_soc) ? $this->socid : $this->fk_soc)); if ($qty < $prod->packaging) { - $qty = $prod->packaging; + $qty = (float) $prod->packaging; } else { - if (!empty($prod->packaging) && ($qty % $prod->packaging) > 0) { - $coeff = intval($qty / $prod->packaging) + 1; - $qty = $prod->packaging * $coeff; + if (!empty($prod->packaging) && (fmod((float) $qty, (float) $prod->packaging) > 0.000001)) { + $coeff = intval((float) $qty / $prod->packaging) + 1; + $qty = (float) $prod->packaging * $coeff; setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs'); } } @@ -2962,11 +2962,11 @@ public function updateline($rowid, $desc, $pu, $qty, $remise_percent, $txtva, $t $this->line->desc = $desc; // redefine quantity according to packaging - if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) { + if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) { if ($qty < $this->line->packaging) { $qty = $this->line->packaging; } else { - if (!empty($this->line->packaging) && ($qty % $this->line->packaging) > 0) { + if (!empty($this->line->packaging) && is_numeric($this->line->packaging) && (float) $this->line->packaging > 0 && (fmod((float) $qty, $this->line->packaging) > 0)) { $coeff = intval($qty / $this->line->packaging) + 1; $qty = $this->line->packaging * $coeff; setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'mesgs'); @@ -3869,7 +3869,7 @@ public function fetch($rowid) $objsearchpackage = $this->db->fetch_object($resqlsearchpackage); if ($objsearchpackage) { $this->fk_fournprice = $objsearchpackage->rowid; - $this->packaging = $objsearchpackage->packaging; + $this->packaging = (float) $objsearchpackage->packaging; } } else { $this->error = $this->db->lasterror(); From f7092b43771b115e9d6b0a9f9a4de8c65abae78e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20FRANCE?= Date: Sat, 31 Jan 2026 20:30:12 +0100 Subject: [PATCH 080/172] fix CI v21 (#37035) * fix CI v21 * Update functions.lib.php --- htdocs/core/lib/functions.lib.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 6b7bfbf5fa5e0..014a238a35017 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -1913,7 +1913,7 @@ function dolSlugify($stringtoslugify) /** * Returns text escaped for inclusion into javascript code * - * @param string $stringtoescape String to escape + * @param int|string $stringtoescape String to escape * @param int<0,3> $mode 0=Escape also ' and " into ', 1=Escape ' but not " for usage into 'string', 2=Escape " but not ' for usage into "string", 3=Escape ' and " with \ * @param int $noescapebackslashn 0=Escape also \n. 1=Do not escape \n. * @return string Escaped string. Both ' and " are escaped into ' if they are escaped. @@ -1942,7 +1942,7 @@ function dol_escape_js($stringtoescape, $mode = 0, $noescapebackslashn = 0) $substitjs["'"] = "\\'"; $substitjs['"'] = "\\\""; } - return strtr($stringtoescape, $substitjs); + return strtr((string) $stringtoescape, $substitjs); } /** From eaee0a952ddf1aa5aa4a963600fa037a324b6853 Mon Sep 17 00:00:00 2001 From: ATM-Lucas Date: Mon, 2 Feb 2026 12:05:11 +0100 Subject: [PATCH 081/172] Fix doc preview in comm card --- htdocs/comm/card.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/htdocs/comm/card.php b/htdocs/comm/card.php index e49beb53e0d95..8e61102083c23 100644 --- a/htdocs/comm/card.php +++ b/htdocs/comm/card.php @@ -887,7 +887,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $propal_static->element, $relativepath, 0); + print $formfile->showPreview($file_list, $propal_static->element, $relativepath, 0, '&entity=' . $objp->entity); } // $filename = dol_sanitizeFileName($objp->ref); // $filedir = $conf->propal->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref); @@ -999,7 +999,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $commande_static->element, $relativepath, 0, $param); + print $formfile->showPreview($file_list, $commande_static->element, $relativepath, 0, '&entity=' . $objp->entity); } // $filename = dol_sanitizeFileName($objp->ref); // $filedir = $conf->order->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref); @@ -1093,7 +1093,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $sendingstatic->element, $relativepath, 0, $param); + print $formfile->showPreview($file_list, $sendingstatic->element, $relativepath, 0, '&entity=' . $objp->entity); } // $filename = dol_sanitizeFileName($objp->ref); // $filedir = $conf->expedition->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref); @@ -1199,7 +1199,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $contrat->element, $relativepath, 0); + print $formfile->showPreview($file_list, $contrat->element, $relativepath, 0, '&entity=' . $objp->entity); } } // $filename = dol_sanitizeFileName($objp->ref); @@ -1290,7 +1290,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $fichinter_static->element, $relativepath, 0); + print $formfile->showPreview($file_list, $fichinter_static->element, $relativepath, 0, '&entity=' . $objp->entity); } // $filename = dol_sanitizeFileName($objp->ref); // $filedir = $conf->fichinter->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref); @@ -1490,7 +1490,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $facturestatic->element, $relativepath, 0); + print $formfile->showPreview($file_list, $facturestatic->element, $relativepath, 0, '&entity=' . $objp->entity); } // $filename = dol_sanitizeFileName($objp->ref); // $filedir = $conf->facture->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref); From 72ed29a5990b778e2093d72988ba1e08760c1099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Fali=C3=A8re?= <121813548+BenjaminFlr@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:50:21 +0100 Subject: [PATCH 082/172] FIX(API, thirdparties): get fixed amount discounts (#37068) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benjamin Falière --- htdocs/societe/class/api_thirdparties.class.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/htdocs/societe/class/api_thirdparties.class.php b/htdocs/societe/class/api_thirdparties.class.php index 351a178d318ef..7a19427f728b7 100644 --- a/htdocs/societe/class/api_thirdparties.class.php +++ b/htdocs/societe/class/api_thirdparties.class.php @@ -1,8 +1,9 @@ - * Copyright (C) 2018 Pierre Chéné - * Copyright (C) 2019 Cedric Ancelin +/* Copyright (C) 2015 Jean-François Ferry + * Copyright (C) 2018 Pierre Chéné + * Copyright (C) 2019 Cedric Ancelin * Copyright (C) 2020-2021 Frédéric France + * 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 @@ -1052,8 +1053,9 @@ public function getFixedAmountDiscounts($id, $filter = "none", $sortfield = "f.t $sql = "SELECT f.ref, f.type as factype, re.fk_facture_source, re.rowid, re.amount_ht, re.amount_tva, re.amount_ttc, re.description, re.fk_facture, re.fk_facture_line"; - $sql .= " FROM ".MAIN_DB_PREFIX."societe_remise_except as re, ".MAIN_DB_PREFIX."facture as f"; - $sql .= " WHERE f.rowid = re.fk_facture_source AND re.fk_soc = ".((int) $id); + $sql .= " FROM ".MAIN_DB_PREFIX."societe_remise_except as re"; + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as f ON f.rowid = re.fk_facture_source"; + $sql .= " WHERE re.fk_soc = ".((int) $id); if ($filter == "available") { $sql .= " AND re.fk_facture IS NULL AND re.fk_facture_line IS NULL"; } From 07a3d0e4b7d5e1d1d4e95cc6ce8ce61ae7d18821 Mon Sep 17 00:00:00 2001 From: Eric Seigne Date: Thu, 5 Feb 2026 09:43:02 +0100 Subject: [PATCH 083/172] auto assign PR on 18.0 / Unable to resolve action cli/gh-action, repository not found --- .github/workflows/pr-18.yaml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-18.yaml b/.github/workflows/pr-18.yaml index 379657c529358..e606aa233e117 100644 --- a/.github/workflows/pr-18.yaml +++ b/.github/workflows/pr-18.yaml @@ -50,8 +50,21 @@ jobs: uses: actions/checkout@v4 # 3) Install / configure GitHub CLI (gh) on the runner - - name: Setup GitHub CLI (gh) - uses: cli/gh-action@v3 + #- name: Setup GitHub CLI (gh) + # uses: cli/gh-action@v3 + # -> Error: Unable to resolve action cli/gh-action, repository not found + - name: Setup GitHub CLI (gh) (install locally) + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y gh || { + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg + sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null + sudo apt-get update + sudo apt-get install -y gh + } + gh --version # Debug information (useful for diagnostics) - name: Debug info From 0f378d5ecfb56f638487815d4e7966021ddfbd9b Mon Sep 17 00:00:00 2001 From: ThomasNgr-OpenDSI Date: Thu, 5 Feb 2026 12:52:50 +0100 Subject: [PATCH 084/172] Fix : PR #36804 includes a regression that blocks navigation between pages (#36821) --- htdocs/product/inventory/inventory.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/htdocs/product/inventory/inventory.php b/htdocs/product/inventory/inventory.php index 494fb9d082247..5ced81e912998 100644 --- a/htdocs/product/inventory/inventory.php +++ b/htdocs/product/inventory/inventory.php @@ -89,10 +89,6 @@ $sortorder = "ASC"; } -// Sort by warehouse/product or product/warehouse, then by batch. -$sortfield .= ',' . ($sortfield == 'e.ref' ? 'p.ref' : 'e.ref') . ',id.batch,id.rowid'; -$sortorder .= ',' . $sortorder.",ASC,ASC"; - // Fetch optionals attributes and labels $extrafields->fetch_name_optionals_label($object->table_element); @@ -126,6 +122,9 @@ $paramwithsearch .= '&limit='.((int) $limit); } +// Sort by warehouse/product or product/warehouse +$sortfield .= ',' . ($sortfield == 'e.ref' ? 'p.ref' : 'e.ref') . ',id.batch,id.rowid'; +$sortorder .= ',' . $sortorder.",ASC,ASC"; if (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) { $permissiontoadd = $user->hasRight('stock', 'creer'); From 66c552ccb2bd84217322e88beefe246bb3008c68 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 6 Feb 2026 02:39:30 +0100 Subject: [PATCH 085/172] Removed not used file --- .scrutinizer.yml | 231 ----------------------------------------------- 1 file changed, 231 deletions(-) delete mode 100644 .scrutinizer.yml diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index c1cb2e853f180..0000000000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,231 +0,0 @@ -# .scrutinizer.yml -#build: -# - php-scrutinizer-run -build: - nodes: - analysis: - tests: - override: - - command: php-scrutinizer-run - idle_timeout: 8000 - -imports: - - javascript - - php - -filter: - excluded_paths: - - build/* - - dev/* - - doc/* - - documents/* - - node_modules/* - - test/* - dependency_paths: - - htdocs/includes/* - paths: - - htdocs/* - - scripts/* - -tools: - # php_analyzer. Doc on https://scrutinizer-ci.com/docs/tools/php/php-analyzer/ - php_analyzer: - enabled: true - extensions: - - php - dependency_paths: - - htdocs/includes/ - filter: - excluded_paths: - - build/* - - dev/* - - doc/* - - documents/* - - htdocs/includes/* - - htdocs/core/class/lessc.class.php - - node_modules/* - - test/* - paths: - - htdocs/ - - scripts/ - config: - parameter_reference_check: - enabled: true - checkstyle: - enabled: false - no_trailing_whitespace: true - naming: - enabled: true - local_variable: ^[a-z][a-zA-Z0-9]*$ - abstract_class_name: ^Abstract|Factory$ - utility_class_name: Utils?$ - constant_name: ^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$ - property_name: ^[a-z][a-zA-Z0-9]*$ - method_name: ^(?:[a-z]|__)[a-zA-Z0-9]*$ - parameter_name: ^[a-z][a-zA-Z0-9]*$ - interface_name: ^[A-Z][a-zA-Z0-9]*Interface$ - type_name: ^[A-Z][a-zA-Z0-9]*$ - exception_name: ^[A-Z][a-zA-Z0-9]*Exception$ - isser_method_name: ^(?:is|has|should|may|supports) - unreachable_code: - enabled: true - check_access_control: - enabled: true - typo_checks: - enabled: true - check_variables: - enabled: true - check_calls: - enabled: true - too_many_arguments: true - missing_argument: true - argument_type_checks: lenient # Allowed Values: "disabled", "lenient", "strict" - suspicious_code: - enabled: true - overriding_parameter: false - overriding_closure_use: true - parameter_closure_use_conflict: true - parameter_multiple_times: true - non_existent_class_in_instanceof_check: true - non_existent_class_in_catch_clause: true - assignment_of_null_return: true - non_commented_switch_fallthrough: true - non_commented_empty_catch_block: true - overriding_private_members: true - use_statement_alias_conflict: true - precedence_in_condition_assignment: true - dead_assignments: - enabled: true - verify_php_doc_comments: - enabled: false - parameters: true - return: true - suggest_more_specific_types: true - ask_for_return_if_not_inferrable: true - ask_for_param_type_annotation: true - loops_must_use_braces: - enabled: true - check_usage_context: - enabled: true - simplify_boolean_return: - enabled: false - phpunit_checks: - enabled: false - reflection_checks: - enabled: true - - # Checks Common Precedence Mistakes - precedence_checks: - enabled: true - assignment_in_condition: true - comparison_of_bit_result: true - basic_semantic_checks: - enabled: true - # Disabled unused code. In most cases, we want to keep it. - unused_code: - enabled: false - deprecation_checks: - enabled: true - useless_function_calls: - enabled: true - metrics_lack_of_cohesion_methods: - enabled: true - metrics_coupling: - enabled: true - stable_code: - namespace_prefixes: [] - classes: [] - doctrine_parameter_binding: - enabled: false - doctrine_entity_manager_injection: - enabled: false - symfony_request_injection: - enabled: false - doc_comment_fixes: - enabled: true - reflection_fixes: - enabled: false - use_statement_fixes: - enabled: true - remove_unused: true - # Whether you would like multiple imports in one USE statement to be preserved, e.g. ``use A, B;``. - preserve_multiple: false - # Whether you would like to preserve blank lines between use statements. - preserve_blanklines: false - order_alphabetically: false - # To use specific config for a specific path, use path_configs: (see example on page https://scrutinizer-ci.com/docs/configuration/tool_config_structure) - - # php_depend - php_pdepend: - enabled: false - configuration_file: null - suffixes: - - php - excluded_dirs: { } - filter: - excluded_paths: - - 'build/*' - - 'dev/*' - - 'doc/*' - - 'test/*' - - 'htdocs/includes/*' - paths: { } - - # change tracking - php_changetracking: - enabled: false - bug_patterns: - - '\bfix(?:es|ed)?\b' - feature_patterns: - - '\badd(?:s|ed)?\b' - - '\bimplement(?:s|ed)?\b' - filter: - excluded_paths: - - 'build/*' - - 'dev/*' - - 'doc/*' - - 'documents/*' - - 'htdocs/includes/*' - - 'node_modules/*' - - 'test/*' - paths: { } - - # Similar code detection - php_sim: - enabled: false - min_mass: 30 - filter: - excluded_paths: - - 'build/*' - - 'dev/*' - - 'doc/*' - - 'documents/*' - - 'htdocs/includes/*' - - 'node_modules/*' - - 'test/*' - paths: { } - - # Coding-Style / Bug Detection - js_hint: - enabled: false - use_native_config: true - extensions: - - js - filter: - excluded_paths: - - 'build/*' - - 'dev/*' - - 'doc/*' - - 'documents/*' - - 'htdocs/includes/*' - - 'node_modules/*' - - 'test/*' - paths: { } - config: { } - path_configs: { } - - -before_commands: { } -after_commands: { } -artifacts: { } -build_failure_conditions: { } From 59ce599ba55c6b1ed5e600908fd1ac509d433358 Mon Sep 17 00:00:00 2001 From: Vincent Penel Date: Fri, 6 Feb 2026 12:18:36 +0100 Subject: [PATCH 086/172] set oldCopy and Status (#37117) * set oldCopy and Status * Update ticket.class.php --------- Co-authored-by: Laurent Destailleur --- htdocs/ticket/class/ticket.class.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/htdocs/ticket/class/ticket.class.php b/htdocs/ticket/class/ticket.class.php index 187867c54c35f..e821b4e80d4f2 100644 --- a/htdocs/ticket/class/ticket.class.php +++ b/htdocs/ticket/class/ticket.class.php @@ -1510,11 +1510,12 @@ public function markAsRead($user, $notrigger = 0) if ($this->statut != self::STATUS_CANCELED) { // no closed $this->oldcopy = dol_clone($this); + $this->status = Ticket::STATUS_READ; $this->db->begin(); $sql = "UPDATE ".MAIN_DB_PREFIX."ticket"; - $sql .= " SET fk_statut = ".Ticket::STATUS_READ.", date_read = '".$this->db->idate(dol_now())."'"; + $sql .= " SET fk_statut = ".$this->status .", date_read = '".$this->db->idate(dol_now())."'"; $sql .= " WHERE rowid = ".((int) $this->id); dol_syslog(get_class($this)."::markAsRead"); @@ -1538,12 +1539,14 @@ public function markAsRead($user, $notrigger = 0) } else { $this->db->rollback(); $this->error = join(',', $this->errors); + $this->status = $this->oldcopy->status; dol_syslog(get_class($this)."::markAsRead ".$this->error, LOG_ERR); return -1; } } else { $this->db->rollback(); $this->error = $this->db->lasterror(); + $this->status = $this->oldcopy->status; dol_syslog(get_class($this)."::markAsRead ".$this->error, LOG_ERR); return -1; } @@ -1743,9 +1746,11 @@ public function close(User $user, $mode = 0) if ($this->fk_statut != Ticket::STATUS_CLOSED && $this->fk_statut != Ticket::STATUS_CANCELED) { // not closed $this->db->begin(); + $this->oldcopy = dol_clone($this); + $this->status = ($mode ? Ticket::STATUS_CANCELED : Ticket::STATUS_CLOSED); $sql = "UPDATE ".MAIN_DB_PREFIX."ticket"; - $sql .= " SET fk_statut=".($mode ? Ticket::STATUS_CANCELED : Ticket::STATUS_CLOSED).", progress=100, date_close='".$this->db->idate(dol_now())."'"; + $sql .= " SET fk_statut=".$this->status.", progress=100, date_close='".$this->db->idate(dol_now())."'"; $sql .= " WHERE rowid = ".((int) $this->id); dol_syslog(get_class($this)."::close mode=".$mode); From 173c6865cb73fafdd736f2938e41c589d8cb8b15 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 6 Feb 2026 13:05:21 +0100 Subject: [PATCH 087/172] Test CI --- .github/workflows/pr-18-testldr.yaml | 79 ++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .github/workflows/pr-18-testldr.yaml diff --git a/.github/workflows/pr-18-testldr.yaml b/.github/workflows/pr-18-testldr.yaml new file mode 100644 index 0000000000000..a04673417c356 --- /dev/null +++ b/.github/workflows/pr-18-testldr.yaml @@ -0,0 +1,79 @@ +# Action to prepare the github action +# Go on Dolibarr - Settings - Developer settings - Enter a name + webhook to disable + Permissions (see app test) + Can install by any account +# Click on generate the private keys +# Click on Install application - choose user of the Organization +# Go on Organisation - Secret and variables and create a secret PR18_SECRET_KEY and copy the content of received private key. Choose the repository access to "Repository Dolibarr". +# Go on Organisation - Secret and variables and create a variable PR18_APP_ID and copy the ID of the previously create ID. Choose the repository access to "Repository Dolibarr". +# + +name: Set reviewer and label for v18 +on: + pull_request_target: + types: [opened, synchronize, reopened] + branches: + - "18.0" + push: + branches: + - "18.0" + +permissions: + pull-requests: write + issues: write + +jobs: + assign-and-label-v18: + runs-on: ubuntu-latest + + steps: + #- name: Install GitHub CLI + # run: sudo apt-get install gh + + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.PR18_APP_ID }} + private-key: ${{ secrets.PR18_SECRET_KEY }} + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Assign Tag + env: + GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} + prid: ${{ github.event.pull_request.number }} + prid2: ${{ github.event.pull_request_target.number }} + url: ${{ github.event.pull_request.html_url }} + url2: ${{ github.event.pull_request_target.html_url }} + run: | + echo "env.prid=${{env.prid}}" + echo "env.prid=${{env.prid2}}" + echo "env.url=${{env.url}}" + echo "env.url2=${{env.url2}}" + gh pr edit "${{env.prid}}" --add-label "Issue for v18 maintenance Team" + + - name: Set reviewers except PR author + id: set-reviewers + run: | + # Liste des reviewers à ajuster selon équipe + REVIEWERS=("lvessiller-opendsi" "rycks") + AUTHOR="${{ github.event.pull_request.user.login }}" + FINAL_REVIEWERS=() + for reviewer in "${REVIEWERS[@]}"; do + if [ "$reviewer" != "$AUTHOR" ]; then + FINAL_REVIEWERS+=("$reviewer") + fi + done + echo "AUTHOR=$AUTHOR" + echo "reviewers=$(IFS=, ; echo "${FINAL_REVIEWERS[*]}")" >> $GITHUB_OUTPUT + + - name: Assign reviewers + if: steps.set-reviewers.outputs.reviewers != '' + env: + GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} + prid: ${{ github.event.pull_request.number }} + url: ${{ github.event.pull_request.html_url }} + reviewers: ${{ steps.set-reviewers.outputs.reviewers }} + run: | + echo "Assigning reviewers: ${{env.reviewers}}" + gh pr edit "${{env.prid}}" --add-reviewer "${{env.reviewers}}" From da0ece345fb5fd72119c0aeda83f2f2bc3b59740 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 6 Feb 2026 13:06:16 +0100 Subject: [PATCH 088/172] Fix label --- .github/workflows/pr-18-testldr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-18-testldr.yaml b/.github/workflows/pr-18-testldr.yaml index a04673417c356..330a853a55f82 100644 --- a/.github/workflows/pr-18-testldr.yaml +++ b/.github/workflows/pr-18-testldr.yaml @@ -6,7 +6,7 @@ # Go on Organisation - Secret and variables and create a variable PR18_APP_ID and copy the ID of the previously create ID. Choose the repository access to "Repository Dolibarr". # -name: Set reviewer and label for v18 +name: Set reviewer and label for v18 (test ldr) on: pull_request_target: types: [opened, synchronize, reopened] From 2b3ae6c2d7d4ff3ccbf9df06b71c8e8d3168dfd3 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 6 Feb 2026 13:16:16 +0100 Subject: [PATCH 089/172] Add logs --- .github/workflows/pr-18-testldr.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr-18-testldr.yaml b/.github/workflows/pr-18-testldr.yaml index 330a853a55f82..6babdf86b7662 100644 --- a/.github/workflows/pr-18-testldr.yaml +++ b/.github/workflows/pr-18-testldr.yaml @@ -28,6 +28,9 @@ jobs: #- name: Install GitHub CLI # run: sudo apt-get install gh + - name: Debug GitHub Event + run: cat $GITHUB_EVENT_PATH + - name: Generate a token id: generate-token uses: actions/create-github-app-token@v2 From 251b57641b1c4482b7c3efe809b176dc7e806365 Mon Sep 17 00:00:00 2001 From: atm-lucas <121817516+atm-lucasmantegari@users.noreply.github.com> Date: Sat, 7 Feb 2026 22:53:52 +0100 Subject: [PATCH 090/172] fix timespent in timeline (#37132) --- htdocs/projet/tasks/time.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/htdocs/projet/tasks/time.php b/htdocs/projet/tasks/time.php index 92822e76c21e8..93ef6997fd446 100644 --- a/htdocs/projet/tasks/time.php +++ b/htdocs/projet/tasks/time.php @@ -332,10 +332,12 @@ $object->timespent_duration = GETPOSTINT("new_durationhour") * 60 * 60; // We store duration in seconds $object->timespent_duration += (GETPOSTINT("new_durationmin") ? GETPOSTINT('new_durationmin') : 0) * 60; // We store duration in seconds if (GETPOST("timelinehour") != '' && GETPOST("timelinehour") >= 0) { // If hour was entered - $object->timespent_date = dol_mktime(GETPOSTINT("timelinehour"), GETPOSTINT("timelinemin"), 0, GETPOSTINT("timelinemonth"), GETPOSTINT("timelineday"), GETPOSTINT("timelineyear")); + $object->timespent_date = dol_mktime(12, 0, 0, GETPOSTINT("timelinemonth"), GETPOSTINT("timelineday"), GETPOSTINT("timelineyear")); + $object->timespent_datehour = dol_mktime(GETPOSTINT("timelinehour"), GETPOSTINT("timelinemin"), 0, GETPOSTINT("timelinemonth"), GETPOSTINT("timelineday"), GETPOSTINT("timelineyear")); $object->timespent_withhour = 1; } elseif (!empty($timespent_date)) { $object->timespent_date = $timespent_date; + $object->timespent_datehour = $timespent_date; $object->timespent_withhour = 0; } $object->timespent_fk_user = GETPOSTINT("userid_line"); From 7bb57721bb2aaa950308f9addd1b9c0b21109506 Mon Sep 17 00:00:00 2001 From: Alexandre SPANGARO Date: Sun, 8 Feb 2026 14:29:25 +0100 Subject: [PATCH 091/172] FIX Invoice - Don't show discount on credit note type (#37145) --- htdocs/core/modules/facture/doc/pdf_sponge.modules.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/htdocs/core/modules/facture/doc/pdf_sponge.modules.php b/htdocs/core/modules/facture/doc/pdf_sponge.modules.php index 8646097ae0321..96db1f14e936a 100644 --- a/htdocs/core/modules/facture/doc/pdf_sponge.modules.php +++ b/htdocs/core/modules/facture/doc/pdf_sponge.modules.php @@ -1774,7 +1774,8 @@ protected function drawTotalTable(&$pdf, $object, $deja_regle, $posy, $outputlan } } if ($total_line_remise > 0) { - if (!getDolGlobalString('MAIN_HIDE_AMOUNT_DISCOUNT')) { + // Show discount except on credit note type invoices + if (!getDolGlobalString('MAIN_HIDE_AMOUNT_DISCOUNT') && $object->type != 2) { $pdf->SetFillColor(255, 255, 255); $pdf->SetXY($col1x, $tab2_top + $tab2_hl); $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("TotalDiscount").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("TotalDiscount") : ''), 0, 'L', 1); @@ -1782,8 +1783,8 @@ protected function drawTotalTable(&$pdf, $object, $deja_regle, $posy, $outputlan $pdf->MultiCell($largcol2, $tab2_hl, price($total_line_remise_print, 0, $outputlangs), 0, 'R', 1); $index++; } - // Show total NET before discount - if (!getDolGlobalString('MAIN_HIDE_AMOUNT_BEFORE_DISCOUNT')) { + // Show total NET before discount except on credit note type invoices + if (!getDolGlobalString('MAIN_HIDE_AMOUNT_BEFORE_DISCOUNT') && $object->type != 2) { $pdf->SetFillColor(255, 255, 255); $pdf->SetXY($col1x, $tab2_top); $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("TotalHTBeforeDiscount").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("TotalHTBeforeDiscount") : ''), 0, 'L', 1); From 94d4f2661dbca1c3961e6d927cb6c41e70526749 Mon Sep 17 00:00:00 2001 From: Eric - CAP-REL <1468823+rycks@users.noreply.github.com> Date: Sun, 8 Feb 2026 15:38:36 +0100 Subject: [PATCH 092/172] MEMBER_ADDON does not exists, MEMBER_CODEMEMBER_ADDON is the right key (#37151) --- htdocs/core/modules/modAdherent.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/modules/modAdherent.class.php b/htdocs/core/modules/modAdherent.class.php index 6e2aeb58c134c..928620bcb8abe 100644 --- a/htdocs/core/modules/modAdherent.class.php +++ b/htdocs/core/modules/modAdherent.class.php @@ -363,8 +363,8 @@ public function __construct($db) $this->import_convertvalue_array[$r] = array( 'a.ref'=>array( 'rule'=>'getrefifauto', - 'class'=>(empty($conf->global->MEMBER_ADDON) ? 'mod_member_simple' : $conf->global->MEMBER_ADDON), - 'path'=>"/core/modules/member/".(empty($conf->global->MEMBER_ADDON) ? 'mod_member_simple' : $conf->global->MEMBER_ADDON).'.php' + 'class'=>(empty($conf->global->MEMBER_CODEMEMBER_ADDON) ? 'mod_member_simple' : $conf->global->MEMBER_CODEMEMBER_ADDON), + 'path'=>"/core/modules/member/".(empty($conf->global->MEMBER_CODEMEMBER_ADDON) ? 'mod_member_simple' : $conf->global->MEMBER_CODEMEMBER_ADDON).'.php' ), 'a.state_id' => array( 'rule' => 'fetchidfromcodeid', From 7b5ddac96354e3b992741419ed1e94a07a2cda01 Mon Sep 17 00:00:00 2001 From: kkhelifa-opendsi Date: Sun, 8 Feb 2026 22:25:58 +0100 Subject: [PATCH 093/172] FIX: Backport fix on v20 for result page of compta (/compta/resultat/index.php) (#37127) * FIX: Backport fix on v20 for result page of compta (/compta/resultat/index.php). The expense report were not included when the module was activated * Update index.php --------- Co-authored-by: Laurent Destailleur --- htdocs/compta/resultat/index.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/compta/resultat/index.php b/htdocs/compta/resultat/index.php index 6a1fd134562d9..b7de1fe19ebd7 100644 --- a/htdocs/compta/resultat/index.php +++ b/htdocs/compta/resultat/index.php @@ -45,7 +45,7 @@ $nbofyear = 4; -// Change this to test different cases of setup +// Change this to test different cases of setup. //$conf->global->SOCIETE_FISCAL_MONTH_START = 7; @@ -678,7 +678,7 @@ * Expense reports */ -if (!isModEnabled('expensereport') && ($modecompta == 'CREANCES-DETTES' || $modecompta == "RECETTES-DEPENSES")) { +if (isModEnabled('expensereport') && ($modecompta == 'CREANCES-DETTES' || $modecompta == "RECETTES-DEPENSES")) { $langs->load('trips'); if ($modecompta == 'CREANCES-DETTES') { From 0cc80e65ed41be9546ec85b1e7afd01bdbbb9947 Mon Sep 17 00:00:00 2001 From: Alexandre SPANGARO Date: Mon, 9 Feb 2026 18:33:54 +0100 Subject: [PATCH 094/172] FIX SQL Error on VAT not defined in product card (#37159) --- htdocs/core/class/html.form.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 8a285587b79d2..e8b04307c2f0c 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -15,7 +15,7 @@ * Copyright (C) 2012-2016 Marcos García * Copyright (C) 2012 Cedric Salvador * Copyright (C) 2012-2015 Raphaël Doursenaud - * Copyright (C) 2014-2023 Alexandre Spangaro + * Copyright (C) 2014-2026 Alexandre Spangaro * Copyright (C) 2018-2022 Ferran Marcet * Copyright (C) 2018-2021 Frédéric France * Copyright (C) 2018 Nicolas ZABOURI @@ -6446,7 +6446,7 @@ public function load_cache_vatrates($country_code) if (!empty($user) && $user->admin && preg_match('/\'(..)\'/', $country_code, $reg)) { $langs->load("errors"); $new_country_code = $reg[1]; - $country_id = dol_getIdFromCode($this->db, $new_country_code, 'c_pays', 'code', 'rowid'); + $country_id = dol_getIdFromCode($this->db, $new_country_code, 'c_country', 'code', 'rowid'); $this->error .= '
'.$langs->trans("ErrorFixThisHere", DOL_URL_ROOT.'/admin/dict.php?id=10'.($country_id > 0 ? '&countryidforinsert='.$country_id : '')); } $this->error .= ''; From 0b8e0c4765d79a37314d2a6875fdd546ad2c15b1 Mon Sep 17 00:00:00 2001 From: ThomasNgr-OpenDSI Date: Tue, 10 Feb 2026 15:30:40 +0100 Subject: [PATCH 095/172] FIX display of DLC/DLUO in tooltip (#37164) --- htdocs/product/stock/class/productlot.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/product/stock/class/productlot.class.php b/htdocs/product/stock/class/productlot.class.php index 7e54fd90622f0..8070fc45a6b20 100644 --- a/htdocs/product/stock/class/productlot.class.php +++ b/htdocs/product/stock/class/productlot.class.php @@ -956,10 +956,10 @@ public function getTooltipContentArray($params) //$datas['divopen'] = '
'; $datas['batch'] = '
'.$langs->trans('Batch').': '.$this->batch; if ($this->eatby && empty($conf->global->PRODUCT_DISABLE_EATBY)) { - $datas['eatby'] = '
'.$langs->trans('EatByDate').': '.dol_print_date($this->db->jdate($this->eatby), 'day'); + $datas['eatby'] = '
'.$langs->trans('EatByDate').': '.dol_print_date($this->eatby, 'day'); } if ($this->sellby && empty($conf->global->PRODUCT_DISABLE_SELLBY)) { - $datas['sellby'] = '
'.$langs->trans('SellByDate').': '.dol_print_date($this->db->jdate($this->sellby), 'day'); + $datas['sellby'] = '
'.$langs->trans('SellByDate').': '.dol_print_date($this->sellby, 'day'); } //$datas['divclose'] = '
'; From e43438cc7b82239e1438282437bb6c4c397950fc Mon Sep 17 00:00:00 2001 From: Vincent Penel Date: Tue, 10 Feb 2026 15:40:05 +0100 Subject: [PATCH 096/172] Fix/ticket status (#37129) * set oldCopy and Status * Update ticket.class.php * Question Eldy * Fix event status change --------- Co-authored-by: Laurent Destailleur --- htdocs/core/class/commonobject.class.php | 4 ++++ htdocs/ticket/card.php | 4 ++-- htdocs/ticket/class/ticket.class.php | 2 +- htdocs/ticket/list.php | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index 1561867c85833..385b493b2d78d 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -4478,7 +4478,11 @@ public function setStatut($status, $elementId = null, $elementType = '', $trigke } } + $this->context = array_merge($this->context, array('newstatus' => $status)); + if ($trigkey) { + $this->oldcopy = dol_clone($this); + // Call trigger $result = $this->call_trigger($trigkey, $user); if ($result < 0) { diff --git a/htdocs/ticket/card.php b/htdocs/ticket/card.php index e207479cacc42..9832970bdfaa6 100755 --- a/htdocs/ticket/card.php +++ b/htdocs/ticket/card.php @@ -568,7 +568,7 @@ if ($object->fetch(GETPOST('id', 'int'), '', GETPOST('track_id', 'alpha')) >= 0) { // prevent browser refresh from reopening ticket several times if ($object->status == Ticket::STATUS_CLOSED || $object->status == Ticket::STATUS_CANCELED) { - $res = $object->setStatut(Ticket::STATUS_ASSIGNED); + $res = $object->setStatut(Ticket::STATUS_ASSIGNED, null, '', 'TICKET_MODIFY'); if ($res) { // Log action in ticket logs table //$log_action = $langs->trans('TicketLogReopen'); @@ -626,7 +626,7 @@ if ($object->fetch(GETPOST('id', 'int'), GETPOST('track_id', 'alpha')) >= 0) { $new_status = GETPOST('new_status', 'int'); $old_status = $object->status; - $res = $object->setStatut($new_status); + $res = $object->setStatut($new_status, null, '', 'TICKET_MODIFY'); if ($res) { // Log action in ticket logs table $log_action = $langs->trans('TicketLogStatusChanged', $langs->transnoentities($object->statuts_short[$old_status]), $langs->transnoentities($object->statuts_short[$new_status])); diff --git a/htdocs/ticket/class/ticket.class.php b/htdocs/ticket/class/ticket.class.php index e821b4e80d4f2..fadcb37135847 100644 --- a/htdocs/ticket/class/ticket.class.php +++ b/htdocs/ticket/class/ticket.class.php @@ -2714,7 +2714,7 @@ public function newMessage($user, &$action, $private = 1, $public_area = 0) if (($object->status < self::STATUS_IN_PROGRESS && !$user->socid && !$private) || ($object->status > self::STATUS_IN_PROGRESS && $public_area) ) { - $object->setStatut(3); + $object->setStatut(3, null, '', 'TICKET_MODIFY'); } return 1; } else { diff --git a/htdocs/ticket/list.php b/htdocs/ticket/list.php index 4c4dce5a428e7..1fddd77010c59 100644 --- a/htdocs/ticket/list.php +++ b/htdocs/ticket/list.php @@ -284,7 +284,7 @@ $result = $objecttmp->fetch($toselectid); if ($result > 0) { if ($objecttmp->status == Ticket::STATUS_CLOSED || $objecttmp->status == Ticket::STATUS_CANCELED) { - $result = $objecttmp->setStatut(Ticket::STATUS_ASSIGNED); + $result = $objecttmp->setStatut(Ticket::STATUS_ASSIGNED, null, '', 'TICKET_MODIFY'); if ($result < 0) { setEventMessages($objecttmp->error, $objecttmp->errors, 'errors'); $error++; From 673659de986ecd65929671fbe5dfe263f50bb0aa Mon Sep 17 00:00:00 2001 From: Alexandre SPANGARO Date: Thu, 12 Feb 2026 01:23:06 +0100 Subject: [PATCH 097/172] FIX Accountancy - Update height of the export popup (#37206) --- htdocs/accountancy/bookkeeping/export.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/accountancy/bookkeeping/export.php b/htdocs/accountancy/bookkeeping/export.php index 246b8e99ebe4a..45934b646f670 100644 --- a/htdocs/accountancy/bookkeeping/export.php +++ b/htdocs/accountancy/bookkeeping/export.php @@ -1,7 +1,7 @@ * Copyright (C) 2013-2016 Florian Henry - * Copyright (C) 2013-2024 Alexandre Spangaro + * Copyright (C) 2013-2026 Alexandre Spangaro * Copyright (C) 2022 Lionel Vessiller * Copyright (C) 2016-2017 Laurent Destailleur * Copyright (C) 2018-2021 Frédéric France @@ -778,7 +778,7 @@ ); } - $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans("ExportFilteredList").'...', $langs->trans('ConfirmExportFile'), 'export_fileconfirm', $form_question, '', 1, 420, 600); + $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans("ExportFilteredList").'...', $langs->trans('ConfirmExportFile'), 'export_fileconfirm', $form_question, '', 1, 500, 600); } // Print form confirm From e850355b3034bb46e3fa5257bf8fbaf19441bf2b Mon Sep 17 00:00:00 2001 From: Eric - CAP-REL <1468823+rycks@users.noreply.github.com> Date: Thu, 12 Feb 2026 09:37:04 +0100 Subject: [PATCH 098/172] Refactor GitHub Actions workflow steps Remove depends on gh and only use curl and requests on github api ... --- .github/workflows/pr-18.yaml | 52 +++++++++++++++--------------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/.github/workflows/pr-18.yaml b/.github/workflows/pr-18.yaml index e606aa233e117..39e34ae88f341 100644 --- a/.github/workflows/pr-18.yaml +++ b/.github/workflows/pr-18.yaml @@ -49,23 +49,6 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - # 3) Install / configure GitHub CLI (gh) on the runner - #- name: Setup GitHub CLI (gh) - # uses: cli/gh-action@v3 - # -> Error: Unable to resolve action cli/gh-action, repository not found - - name: Setup GitHub CLI (gh) (install locally) - run: | - set -euo pipefail - sudo apt-get update - sudo apt-get install -y gh || { - curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg - sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null - sudo apt-get update - sudo apt-get install -y gh - } - gh --version - # Debug information (useful for diagnostics) - name: Debug info run: | @@ -74,7 +57,7 @@ jobs: echo "Run id: $GITHUB_RUN_ID" echo "Reviewers configured: $REVIEWERS" - # 4) Add the label to the PR (PR events only) + # 3) Add the label to the PR (PR events only) # -> tolerant to errors: log on failure but do not fail the job - name: Add label to PR (pull_request events only) if: ${{ github.event_name == 'pull_request' }} @@ -82,16 +65,23 @@ jobs: GH_TOKEN: ${{ steps.generate-token.outputs.token }} PR_NUMBER: ${{ github.event.pull_request.number }} V18_LABEL: ${{ env.V18_LABEL }} + REPO: ${{ github.repository }} run: | set -euo pipefail echo "Adding label '$V18_LABEL' to PR #${PR_NUMBER}" - if gh pr edit "$PR_NUMBER" --add-label "$V18_LABEL"; then + response=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + -H "Authorization: Bearer ${GH_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/labels" \ + -d "{\"labels\": [\"${V18_LABEL}\"]}") + + if [ "$response" -eq 200 ]; then echo "Label added successfully." else - echo "Warning: failed to add label '$V18_LABEL' to PR #${PR_NUMBER}. Continuing." + echo "Warning: failed to add label '$V18_LABEL' to PR #${PR_NUMBER} (HTTP $response). Continuing." fi - # 5) Compute final reviewers list excluding the PR author + # 4) Compute final reviewers list excluding the PR author - name: Compute reviewers (exclude PR author) if: ${{ github.event_name == 'pull_request' }} id: set-reviewers @@ -118,7 +108,7 @@ jobs: echo "author=$AUTHOR" >> $GITHUB_OUTPUT echo "Computed reviewers: ${reviewers_csv:-}" - # 6) Assign reviewers one-by-one with fine-grained error handling + # 5) Assign reviewers one-by-one with fine-grained error handling # - try each reviewer, track successes and failures # - fail the step only if none could be added # - succeed if at least one was added (but log failures) @@ -128,8 +118,8 @@ jobs: GH_TOKEN: ${{ steps.generate-token.outputs.token }} PR_NUMBER: ${{ github.event.pull_request.number }} REVIEWERS_CSV: ${{ steps.set-reviewers.outputs.reviewers }} + REPO: ${{ github.repository }} run: | - # Note: avoid 'set -e' to handle per-reviewer failures manually set -uo pipefail IFS=',' read -ra TO_ADD <<< "${REVIEWERS_CSV}" SUCCESS=0 @@ -140,26 +130,28 @@ jobs: continue fi echo "Attempting to add reviewer: $r_trimmed" - if gh pr edit "$PR_NUMBER" --add-reviewer "$r_trimmed"; then + response=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + -H "Authorization: Bearer ${GH_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}/requested_reviewers" \ + -d "{\"reviewers\": [\"${r_trimmed}\"]}") + + if [ "$response" -eq 201 ] || [ "$response" -eq 200 ]; then echo "Added reviewer: $r_trimmed" SUCCESS=$((SUCCESS+1)) else - echo "Warning: failed to add reviewer: $r_trimmed" - echo "Debug: PR view:" - gh pr view "$PR_NUMBER" --json number,title,author,reviewRequests || true + echo "Warning: failed to add reviewer: $r_trimmed (HTTP $response)" FAILED+=("$r_trimmed") fi done if [ $SUCCESS -eq 0 ]; then echo "Error: none of the configured reviewers could be added: ${FAILED[*]:-}" - # Fail the step because no reviewer was added exit 1 else echo "Reviewers added: ${SUCCESS}. Failed to add: ${FAILED[*]:-none}" - # Step succeeds even if some reviewers failed fi - # 7) Push event notice (no PR-specific actions performed) + # 6) Push event notice (no PR-specific actions performed) - name: Push event notice if: ${{ github.event_name == 'push' }} run: | From a8e050834312b1c9a5736f550ab012e941e72434 Mon Sep 17 00:00:00 2001 From: ThomasNgr-OpenDSI Date: Thu, 12 Feb 2026 16:30:45 +0100 Subject: [PATCH 099/172] Sec: Can init a page with php content without permission for php content (#37211) edition (reported by phdwg1410) Co-authored-by: Laurent Destailleur --- htdocs/website/class/websitepage.class.php | 12 ++++++++++++ htdocs/website/index.php | 3 +++ 2 files changed, 15 insertions(+) diff --git a/htdocs/website/class/websitepage.class.php b/htdocs/website/class/websitepage.class.php index 40ca4231d7d09..0e3c84cbb8eda 100644 --- a/htdocs/website/class/websitepage.class.php +++ b/htdocs/website/class/websitepage.class.php @@ -226,6 +226,18 @@ public function create(User $user, $notrigger = false) // Remove spaces and be sure we have main language only $this->lang = preg_replace('/[_-].*$/', '', trim($this->lang)); // en_US or en-US -> en + // Test if page contains dynamic PHP content + if (!$user->hasRight('website', 'writephp')) { + // Check there is no PHP content into the imported file (must be only HTML + JS) + $phpcontent = dolKeepOnlyPhpCode($this->content); + + if ($phpcontent) { + $this->error = 'Error: you try to create a page with PHP content without having permissions for that.'; + $this->errors = $this->error; + return -1; + } + } + return $this->createCommon($user, $notrigger); } diff --git a/htdocs/website/index.php b/htdocs/website/index.php index c67e80dee29a7..e8725ad8b561f 100644 --- a/htdocs/website/index.php +++ b/htdocs/website/index.php @@ -806,6 +806,7 @@ // Remove comments $tmp['content'] = removeHtmlComment($tmp['content']); + /* disable this, moved into the create() method // Check there is no PHP content into the imported file (must be only HTML + JS) $phpcontent = dolKeepOnlyPhpCode($tmp['content']); if ($phpcontent) { @@ -813,6 +814,7 @@ setEventMessages('Error getting '.$urltograb.': file that include PHP content is not allowed', null, 'errors'); $action = 'createcontainer'; } + */ } if (!$error) { @@ -1169,6 +1171,7 @@ } if (!$error) { + // Create page. This also check there is no PHP content if user has no pemrissions for that. $pageid = $objectpage->create($user); if ($pageid <= 0) { $error++; From 30f7f34708bd0cd7752901994a4a2887ddeaf361 Mon Sep 17 00:00:00 2001 From: Alexandre SPANGARO Date: Mon, 16 Feb 2026 19:54:09 +0100 Subject: [PATCH 100/172] FIX Societe - Wrong control on update of VAT Intra (#37224) --- htdocs/societe/class/societe.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/societe/class/societe.class.php b/htdocs/societe/class/societe.class.php index 9ffd25427be05..a78c66f95c65a 100644 --- a/htdocs/societe/class/societe.class.php +++ b/htdocs/societe/class/societe.class.php @@ -8,7 +8,7 @@ * Copyright (C) 2008 Patrick Raguin * Copyright (C) 2010-2018 Juanjo Menent * Copyright (C) 2013 Florian Henry - * Copyright (C) 2013-2023 Alexandre Spangaro + * Copyright (C) 2013-2026 Alexandre Spangaro * Copyright (C) 2013 Peter Fontaine * Copyright (C) 2014-2015 Marcos García * Copyright (C) 2015 Raphaël Doursenaud @@ -1368,7 +1368,7 @@ public function update($id, $user = '', $call_trigger = 1, $allowmodcodeclient = $this->supplier_order_min_amount = price2num($this->supplier_order_min_amount); $this->tva_assuj = trim($this->tva_assuj); - $this->tva_intra = dol_sanitizeFileName($this->tva_intra, ''); + $this->tva_intra = trim($this->tva_intra); $this->vat_reverse_charge = empty($this->vat_reverse_charge) ? '0' : '1'; if (empty($this->status)) { $this->status = 0; From d4ff519b7547443ba1f03d887558150334ba791e Mon Sep 17 00:00:00 2001 From: Vincent Maury Date: Tue, 17 Feb 2026 20:31:03 +0100 Subject: [PATCH 101/172] Fix Product Api Update price (#37209) If product prices is set to "price by client" sell price is never updated by API --- htdocs/product/class/api_products.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/product/class/api_products.class.php b/htdocs/product/class/api_products.class.php index 06c41e0b82abe..fdd2b2bc85472 100644 --- a/htdocs/product/class/api_products.class.php +++ b/htdocs/product/class/api_products.class.php @@ -412,8 +412,8 @@ public function put($id, $request_data = null) $result = $this->product->update($id, DolibarrApiAccess::$user, 1, 'update', $updatetype); - // If price mode is 1 price per product - if ($result > 0 && getDolGlobalString('PRODUCT_PRICE_UNIQ')) { + // If price mode is 1 price per product or price by client + if ($result > 0 && (getDolGlobalString('PRODUCT_PRICE_UNIQ') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES'))) { // We update price only if it was changed $pricemodified = false; if ($this->product->price_base_type != $oldproduct->price_base_type) { From 48cb31224ead39080c8d51411fe79e6313b9412b Mon Sep 17 00:00:00 2001 From: Jyhere Date: Sat, 21 Feb 2026 12:03:15 +0100 Subject: [PATCH 102/172] FIX: MAIN_FEATURES_LEVEL typo errors (#37304) --- htdocs/asset/class/assetdepreciationoptions.class.php | 2 +- htdocs/core/tpl/admin_extrafields_add.tpl.php | 4 ++-- htdocs/core/tpl/admin_extrafields_edit.tpl.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/htdocs/asset/class/assetdepreciationoptions.class.php b/htdocs/asset/class/assetdepreciationoptions.class.php index a8d3e44fe4176..90b4712d68ac7 100644 --- a/htdocs/asset/class/assetdepreciationoptions.class.php +++ b/htdocs/asset/class/assetdepreciationoptions.class.php @@ -303,7 +303,7 @@ public function setDeprecationOptionsFromPost($class_type = 0) $deprecation_options[$mode_key][$field_key] = $field_value; // Validation of fields values - if (getDolGlobalInt('MAIN_FEATURE_LEVEL') >= 1 || getDolGlobalString('MAIN_ACTIVATE_VALIDATION_RESULT')) { + if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 1 || getDolGlobalString('MAIN_ACTIVATE_VALIDATION_RESULT')) { if (!$error && !empty($field_info['validate']) && is_callable(array($this, 'validateField'))) { if (!$this->validateField($mode_info['fields'], $field_key, $value)) { $error++; diff --git a/htdocs/core/tpl/admin_extrafields_add.tpl.php b/htdocs/core/tpl/admin_extrafields_add.tpl.php index 25452bca8db95..b5684d3ff969f 100644 --- a/htdocs/core/tpl/admin_extrafields_add.tpl.php +++ b/htdocs/core/tpl/admin_extrafields_add.tpl.php @@ -195,8 +195,8 @@ function init_typeoffields(type) textwithpicto('', $langs->trans("ExtrafieldParamHelpselect"), 1, 0, '', 0, 2, 'helpvalue1')?> - textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATUREES_LEVEL') > 0 ? '
'.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue2')?>
- textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATUREES_LEVEL') > 0 ? '
'.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue3')?>
+ textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0 ? '
'.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue2')?>
+ textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0 ? '
'.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue3')?>
textwithpicto('', $langs->trans("ExtrafieldParamHelplink").'

'.$langs->trans("Examples").':
'.$listofexamplesforlink, 1, 0, '', 0, 2, 'helpvalue4')?>
textwithpicto('', $langs->trans("ExtrafieldParamHelpPassword"), 1, 0, '', 0, 2, 'helpvalue5')?> textwithpicto('', $langs->trans("ExtrafieldParamHelpSeparator"), 1, 0, '', 0, 2, 'helpvalue6')?> diff --git a/htdocs/core/tpl/admin_extrafields_edit.tpl.php b/htdocs/core/tpl/admin_extrafields_edit.tpl.php index 78e0e8a2da0fb..fadf9b9b3adaf 100644 --- a/htdocs/core/tpl/admin_extrafields_edit.tpl.php +++ b/htdocs/core/tpl/admin_extrafields_edit.tpl.php @@ -257,8 +257,8 @@ function init_typeoffields(type) textwithpicto('', $langs->trans("ExtrafieldParamHelpselect"), 1, 0, '', 0, 2, 'helpvalue1')?> - textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATUREES_LEVEL') > 0 ? '
'.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue2')?>
- textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATUREES_LEVEL') > 0 ? '
'.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue3')?>
+ textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0 ? '
'.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue2')?>
+ textwithpicto('', $langs->trans("ExtrafieldParamHelpsellist").(getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0 ? '
'.$langs->trans("ExtrafieldParamHelpsellist2") : ''), 1, 0, '', 0, 2, 'helpvalue3')?>
textwithpicto('', $langs->trans("ExtrafieldParamHelplink").'

'.$langs->trans("Examples").':
'.$listofexamplesforlink, 1, 0, '', 0, 2, 'helpvalue4')?>
textwithpicto('', $langs->trans("ExtrafieldParamHelpPassword"), 1, 0, '', 0, 2, 'helpvalue5')?> textwithpicto('', $langs->trans("ExtrafieldParamHelpSeparator"), 1, 0, '', 0, 2, 'helpvalue6')?> From f87a5ab46a297522e465951bb0e8a6c88079a5be Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 23 Feb 2026 01:21:39 +0100 Subject: [PATCH 103/172] Remove beautysh --- .pre-commit-config.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 950e65c97d582..160c98b1821e5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -71,14 +71,14 @@ repos: - id: actionlint # Beautify shell scripts - - repo: https://github.com/lovesegfault/beautysh.git - rev: v6.2.1 - hooks: - - id: beautysh - exclude: | - (?x)^(dev/setup/git/hooks/pre-commit - )$ - args: [--tab] + #- repo: https://github.com/lovesegfault/beautysh.git + # rev: v6.2.1 + # hooks: + # - id: beautysh + # exclude: | + # (?x)^(dev/setup/git/hooks/pre-commit + # )$ + # args: [--tab] # Run local script # From 16b2735ca42fcb678b19dbc2f5f4db35d73cc885 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 23 Feb 2026 01:33:35 +0100 Subject: [PATCH 104/172] Fix CI --- htdocs/website/class/websitepage.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/website/class/websitepage.class.php b/htdocs/website/class/websitepage.class.php index ecb34505cf097..f8c9a49e45ad8 100644 --- a/htdocs/website/class/websitepage.class.php +++ b/htdocs/website/class/websitepage.class.php @@ -271,7 +271,7 @@ public function create(User $user, $notrigger = 0) if ($phpcontent) { $this->error = 'Error: you try to create a page with PHP content without having permissions for that.'; - $this->errors = $this->error; + $this->errors[] = $this->error; return -1; } } From d6f4bf8f03403ebaf389132617c9a8b1e6deb1b0 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Mon, 23 Feb 2026 02:10:32 +0100 Subject: [PATCH 105/172] fix: in some case on create invoice when create PDF the lang is not correcly defined because ->thirdparty is not set (#37287) Co-authored-by: Laurent Destailleur --- htdocs/compta/facture/card.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/htdocs/compta/facture/card.php b/htdocs/compta/facture/card.php index 9f2ff4d97ffb6..108da039fa757 100644 --- a/htdocs/compta/facture/card.php +++ b/htdocs/compta/facture/card.php @@ -2125,6 +2125,9 @@ $newlang = GETPOST('lang_id', 'aZ09'); } if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang)) { + if (empty($object->thirdparty)) { + $object->fetch_thirdparty(); + } $newlang = $object->thirdparty->default_lang; } if (!empty($newlang)) { From 125caf1d4be870dde5b99755f1134408f3b6530f Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Mon, 23 Feb 2026 20:06:13 +0100 Subject: [PATCH 106/172] fix: filter on shipping list on mass action (#37281) Co-authored-by: Laurent Destailleur --- htdocs/expedition/list.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/htdocs/expedition/list.php b/htdocs/expedition/list.php index ad28811adeb1a..59d22552d38a2 100644 --- a/htdocs/expedition/list.php +++ b/htdocs/expedition/list.php @@ -557,8 +557,10 @@ if ($search_company) { $param .= "&search_company=".urlencode($search_company); } - if ($search_shipping_method_id) { - $param .= "&search_shipping_method_id=".urlencode($search_shipping_method_id); + if ($search_shipping_method_ids) { + foreach ($search_shipping_method_ids as $value) { + $param .= "&search_shipping_method_ids[]=".urlencode($value); + } } if ($search_tracking) { $param .= "&search_tracking=".urlencode($search_tracking); From ba8ab407fe31feae8485f58787060f16392a87f0 Mon Sep 17 00:00:00 2001 From: ATM-Lucas Date: Thu, 26 Feb 2026 10:10:09 +0100 Subject: [PATCH 107/172] delete & --- htdocs/comm/card.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/htdocs/comm/card.php b/htdocs/comm/card.php index 8e61102083c23..5d71b079bcd93 100644 --- a/htdocs/comm/card.php +++ b/htdocs/comm/card.php @@ -887,7 +887,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $propal_static->element, $relativepath, 0, '&entity=' . $objp->entity); + print $formfile->showPreview($file_list, $propal_static->element, $relativepath, 0, 'entity=' . $objp->entity); } // $filename = dol_sanitizeFileName($objp->ref); // $filedir = $conf->propal->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref); @@ -1199,7 +1199,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $contrat->element, $relativepath, 0, '&entity=' . $objp->entity); + print $formfile->showPreview($file_list, $contrat->element, $relativepath, 0, 'entity=' . $objp->entity); } } // $filename = dol_sanitizeFileName($objp->ref); @@ -1290,7 +1290,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $fichinter_static->element, $relativepath, 0, '&entity=' . $objp->entity); + print $formfile->showPreview($file_list, $fichinter_static->element, $relativepath, 0, 'entity=' . $objp->entity); } // $filename = dol_sanitizeFileName($objp->ref); // $filedir = $conf->fichinter->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref); @@ -1490,7 +1490,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $facturestatic->element, $relativepath, 0, '&entity=' . $objp->entity); + print $formfile->showPreview($file_list, $facturestatic->element, $relativepath, 0, 'entity=' . $objp->entity); } // $filename = dol_sanitizeFileName($objp->ref); // $filedir = $conf->facture->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref); From c5b4d149b5dd579578275c78e7da87b9d4f7531b Mon Sep 17 00:00:00 2001 From: ATM-Lucas Date: Thu, 26 Feb 2026 14:11:00 +0100 Subject: [PATCH 108/172] delete more & --- htdocs/comm/card.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/comm/card.php b/htdocs/comm/card.php index 5d71b079bcd93..65a21665fc59f 100644 --- a/htdocs/comm/card.php +++ b/htdocs/comm/card.php @@ -999,7 +999,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $commande_static->element, $relativepath, 0, '&entity=' . $objp->entity); + print $formfile->showPreview($file_list, $commande_static->element, $relativepath, 0, 'entity=' . $objp->entity); } // $filename = dol_sanitizeFileName($objp->ref); // $filedir = $conf->order->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref); @@ -1093,7 +1093,7 @@ } } $relativepath = dol_sanitizeFileName($objp->ref).'/'.dol_sanitizeFileName($objp->ref).'.pdf'; - print $formfile->showPreview($file_list, $sendingstatic->element, $relativepath, 0, '&entity=' . $objp->entity); + print $formfile->showPreview($file_list, $sendingstatic->element, $relativepath, 0, 'entity=' . $objp->entity); } // $filename = dol_sanitizeFileName($objp->ref); // $filedir = $conf->expedition->multidir_output[$objp->entity].'/'.dol_sanitizeFileName($objp->ref); From 2b0696080c6855ae8eadd1235e72274af5cd06d9 Mon Sep 17 00:00:00 2001 From: atm-lucas <121817516+atm-lucasmantegari@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:24:52 +0100 Subject: [PATCH 109/172] Verif odt template extension (#37366) --- htdocs/core/actions_setmoduleoptions.inc.php | 49 +++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/htdocs/core/actions_setmoduleoptions.inc.php b/htdocs/core/actions_setmoduleoptions.inc.php index 8439660f6c86d..4a56c7fc750c1 100644 --- a/htdocs/core/actions_setmoduleoptions.inc.php +++ b/htdocs/core/actions_setmoduleoptions.inc.php @@ -171,7 +171,54 @@ } } - if ($upload_dir) { + // Restrict uploads for ODT template setup pages. + if (preg_match('/_ADDON_PDF_ODT_PATH$/', $keyforuploaddir) && !empty($_FILES['uploadfile'])) { + $allowedext = array('odt', 'ods'); + $allowedmimes = array( + 'application/vnd.oasis.opendocument.text', + 'application/vnd.oasis.opendocument.spreadsheet', + 'application/zip', + 'application/x-zip-compressed', + 'application/octet-stream' + ); + $files = $_FILES['uploadfile']; + if (!is_array($files['name'])) { + foreach ($files as $key => &$val) { + $val = array($val); + } + unset($val); + } + foreach ($files['name'] as $idx => $filename) { + if (empty($filename)) { + continue; + } + + $extension = strtolower(pathinfo((string) $filename, PATHINFO_EXTENSION)); + if (!in_array($extension, $allowedext, true)) { + $error++; + setEventMessages($langs->trans("ErrorFileNotUploaded").' (Only .odt/.ods templates are allowed)', null, 'errors'); + dol_syslog(__FILE__." ODT template upload refused on extension filename=".$filename, LOG_WARNING); + break; + } + + $detectedmime = ''; + if (!empty($files['tmp_name'][$idx]) && function_exists('finfo_open')) { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + if ($finfo) { + $detectedmime = (string) finfo_file($finfo, $files['tmp_name'][$idx]); + finfo_close($finfo); + } + } + if (!empty($detectedmime) && !in_array(strtolower($detectedmime), $allowedmimes, true)) { + $error++; + setEventMessages($langs->trans("ErrorFileNotUploaded").' (Invalid MIME type for ODT/ODS template)', null, 'errors'); + dol_syslog(__FILE__." ODT template upload refused on mime filename=".$filename." mime=".$detectedmime, LOG_WARNING); + break; + } + } + } + + if ($upload_dir && !$error) { $result = dol_add_file_process($upload_dir, 1, 1, 'uploadfile', ''); if ($result <= 0) { $error++; From e1dedd638bc613cd542e533c2867a2fd0052161c Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 27 Feb 2026 14:32:47 +0100 Subject: [PATCH 110/172] Disable beautysh. Overload of work with this is 100x higher than the gain. Disable definitely. --- .pre-commit-config.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 16927ef7bbc80..fb94f64e8d3d0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -58,21 +58,21 @@ repos: # Check that there are no files that have are the same when uppercased (conflict on windows) - id: check-case-conflict - # Beautify shell scripts + # Git leaks - repo: https://github.com/gitleaks/gitleaks.git rev: v8.16.1 hooks: - id: gitleaks # Beautify shell scripts - - repo: https://github.com/lovesegfault/beautysh.git - rev: v6.2.1 - hooks: - - id: beautysh - exclude: | - (?x)^(dev/setup/git/hooks/pre-commit - )$ - args: [--tab] + #- repo: https://github.com/lovesegfault/beautysh.git + # rev: v6.2.1 + # hooks: + # - id: beautysh + # exclude: | + # (?x)^(dev/setup/git/hooks/pre-commit + # )$ + # args: [--tab] # Run local script # From f09e8916c351f00f01f5267fef0ca081c86cce0b Mon Sep 17 00:00:00 2001 From: Lucas Marcouiller <45882981+Hystepik@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:36:28 +0100 Subject: [PATCH 111/172] Fix bad rounding when spliting discounts (#37354) * Fix bad rounding when spliting discounts * fix ci --------- Co-authored-by: Lucas Marcouiller --- htdocs/comm/remx.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/comm/remx.php b/htdocs/comm/remx.php index c60be020eb960..4b7e90697876b 100644 --- a/htdocs/comm/remx.php +++ b/htdocs/comm/remx.php @@ -84,7 +84,7 @@ $error++; setEventMessages($langs->trans("ErrorFailedToLoadDiscount"), null, 'errors'); } - if (!$error && price2num((float) $amount_ttc_1 + (float) $amount_ttc_2) != $discount->amount_ttc) { + if (!$error && price2num((float) $amount_ttc_1 + (float) $amount_ttc_2, 'MT') != $discount->amount_ttc) { $error++; setEventMessages($langs->trans("TotalOfTwoDiscountMustEqualsOriginal"), null, 'errors'); } From d0860270acd89be3f9cf383a7b984f17af1f93df Mon Sep 17 00:00:00 2001 From: atm-adrien <67913809+atm-adrien@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:40:10 +0100 Subject: [PATCH 112/172] FIX : Entity on group ticket insertion (#37370) --- htdocs/core/modules/modTicket.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/modules/modTicket.class.php b/htdocs/core/modules/modTicket.class.php index 1028706ed6bbb..5228cd1ae74e9 100644 --- a/htdocs/core/modules/modTicket.class.php +++ b/htdocs/core/modules/modTicket.class.php @@ -148,13 +148,13 @@ public function __construct($db) 'tabsql' => array( 'SELECT f.rowid as rowid, f.code, f.pos, f.label, f.active, f.use_default FROM '.MAIN_DB_PREFIX.'c_ticket_type as f', 'SELECT f.rowid as rowid, f.code, f.pos, f.label, f.active, f.use_default FROM '.MAIN_DB_PREFIX.'c_ticket_severity as f', - 'SELECT f.rowid as rowid, f.code, f.pos, f.label, f.active, f.use_default, f.public, f.fk_parent FROM '.MAIN_DB_PREFIX.'c_ticket_category as f', + 'SELECT f.rowid as rowid, f.entity, f.code, f.pos, f.label, f.active, f.use_default, f.public, f.fk_parent FROM '.MAIN_DB_PREFIX.'c_ticket_category as f', 'SELECT f.rowid as rowid, f.code, f.pos, f.label, f.active, f.use_default FROM '.MAIN_DB_PREFIX.'c_ticket_resolution as f' ), 'tabsqlsort' => array("pos ASC", "pos ASC", "pos ASC", "pos ASC"), 'tabfield' => array("code,label,pos,use_default", "code,label,pos,use_default", "code,label,pos,use_default,public,fk_parent", "code,label,pos,use_default"), 'tabfieldvalue' => array("code,label,pos,use_default", "code,label,pos,use_default", "code,label,pos,use_default,public,fk_parent", "code,label,pos,use_default"), - 'tabfieldinsert' => array("code,label,pos,use_default", "code,label,pos,use_default", "code,label,pos,use_default,public,fk_parent", "code,label,pos,use_default"), + 'tabfieldinsert' => array("code,label,pos,use_default", "code,label,pos,use_default", "entity, code,label,pos,use_default,public,fk_parent", "code,label,pos,use_default"), 'tabrowid' => array("rowid", "rowid", "rowid", "rowid"), 'tabcond' => array($conf->ticket->enabled, $conf->ticket->enabled, $conf->ticket->enabled, $conf->ticket->enabled && getDolGlobalString('TICKET_ENABLE_RESOLUTION')), 'tabhelp' => array( From 5077058df523fd193920842d5109711f506b6901 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 2 Mar 2026 15:21:52 +0100 Subject: [PATCH 113/172] Fix CI --- htdocs/comm/action/card.php | 4 ++-- htdocs/website/class/websitepage.class.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/comm/action/card.php b/htdocs/comm/action/card.php index 37b873544a0d2..1e4f2335569eb 100644 --- a/htdocs/comm/action/card.php +++ b/htdocs/comm/action/card.php @@ -805,9 +805,9 @@ // increment date for recurrent events $datep = dol_time_plus_duree($datep, $dayoffset, 'd'); - $datep = dol_time_plus_duree($datep, $monthoffset, 'm'); + $datep = dol_time_plus_duree($datep, $monthoffset, 'm'); // @phan-suppress-current-line PhanPluginSuspiciousParamOrder $datef = dol_time_plus_duree($datef, $dayoffset, 'd'); - $datef = dol_time_plus_duree($datef, $monthoffset, 'm'); + $datef = dol_time_plus_duree($datef, $monthoffset, 'm'); // @phan-suppress-current-line PhanPluginSuspiciousParamOrder } } if (!empty($backtopage) && !$error) { diff --git a/htdocs/website/class/websitepage.class.php b/htdocs/website/class/websitepage.class.php index 6479c26dde05d..fd468c623b649 100644 --- a/htdocs/website/class/websitepage.class.php +++ b/htdocs/website/class/websitepage.class.php @@ -231,7 +231,7 @@ public function create(User $user, $notrigger = 0) if ($phpcontent) { $this->error = 'Error: you try to create a page with PHP content without having permissions for that.'; - $this->errors = $this->error; + $this->errors[] = $this->error; return -1; } } From 293b5a0778755c7e7804f2de75ee967c2277c5f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnter=20Lukas?= Date: Mon, 2 Mar 2026 20:10:33 +0100 Subject: [PATCH 114/172] Fix #37388 Change GETPOSTINT to GETPOST for form question (#37390) Co-authored-by: Laurent Destailleur --- htdocs/compta/paiement.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/compta/paiement.php b/htdocs/compta/paiement.php index eb761f7aa887b..d186be0275174 100644 --- a/htdocs/compta/paiement.php +++ b/htdocs/compta/paiement.php @@ -165,7 +165,7 @@ } } - $formquestion[$i++] = array('type' => 'hidden', 'name' => $key, 'value' => GETPOSTINT($key)); + $formquestion[$i++] = array('type' => 'hidden', 'name' => $key, 'value' => GETPOST($key)); } } From 749b45a16dcf37daf8aace158001836d825a99f5 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 3 Mar 2026 14:24:04 +0100 Subject: [PATCH 115/172] Fix from may be empty --- htdocs/core/class/notify.class.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/htdocs/core/class/notify.class.php b/htdocs/core/class/notify.class.php index 5897b18fa8318..350c4d890714b 100644 --- a/htdocs/core/class/notify.class.php +++ b/htdocs/core/class/notify.class.php @@ -649,7 +649,10 @@ public function send($notifcode, $object, $filename_list = array(), $mimetype_li if (getDolGlobalString('MAIN_APPLICATION_TITLE')) { $application = getDolGlobalString('MAIN_APPLICATION_TITLE'); } - $from = getDolGlobalString('NOTIFICATION_EMAIL_FROM', getDolGlobalString('MAIN_MAIL_EMAIL_FROM')); + $from = getDolGlobalString('NOTIFICATION_EMAIL_FROM'); + if (empty($from)) { + $from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM'); + } $object_type = ''; $link = ''; $num = 0; From e0492228779700d23bc1aaecac41c78e7f8a5fd4 Mon Sep 17 00:00:00 2001 From: Eric Seigne Date: Thu, 5 Mar 2026 09:55:51 +0100 Subject: [PATCH 116/172] github.event_name is pull_request_target not pull_request --- .github/workflows/pr-18.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pr-18.yaml b/.github/workflows/pr-18.yaml index 39e34ae88f341..4c06d591eaddf 100644 --- a/.github/workflows/pr-18.yaml +++ b/.github/workflows/pr-18.yaml @@ -60,7 +60,7 @@ jobs: # 3) Add the label to the PR (PR events only) # -> tolerant to errors: log on failure but do not fail the job - name: Add label to PR (pull_request events only) - if: ${{ github.event_name == 'pull_request' }} + if: ${{ github.event_name == 'pull_request_target' }} env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} PR_NUMBER: ${{ github.event.pull_request.number }} @@ -74,7 +74,7 @@ jobs: -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/labels" \ -d "{\"labels\": [\"${V18_LABEL}\"]}") - + if [ "$response" -eq 200 ]; then echo "Label added successfully." else @@ -83,7 +83,7 @@ jobs: # 4) Compute final reviewers list excluding the PR author - name: Compute reviewers (exclude PR author) - if: ${{ github.event_name == 'pull_request' }} + if: ${{ github.event_name == 'pull_request_target' }} id: set-reviewers run: | set -euo pipefail @@ -113,7 +113,7 @@ jobs: # - fail the step only if none could be added # - succeed if at least one was added (but log failures) - name: Assign reviewers on PR (per-reviewer, tolerant errors) - if: ${{ github.event_name == 'pull_request' && steps.set-reviewers.outputs.reviewers != '' }} + if: ${{ github.event_name == 'pull_request_target' && steps.set-reviewers.outputs.reviewers != '' }} env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} PR_NUMBER: ${{ github.event.pull_request.number }} @@ -135,7 +135,7 @@ jobs: -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}/requested_reviewers" \ -d "{\"reviewers\": [\"${r_trimmed}\"]}") - + if [ "$response" -eq 201 ] || [ "$response" -eq 200 ]; then echo "Added reviewer: $r_trimmed" SUCCESS=$((SUCCESS+1)) From cf11ecd0859a2867a1f2c691d61ae7a8564ff375 Mon Sep 17 00:00:00 2001 From: Bahfir Abbes Date: Sun, 9 Jul 2023 15:11:16 +0200 Subject: [PATCH 117/172] Fix:warehouses table name is entrepot which does not hold an fk_soc field, so _checkAccessToResource returns always false and must be disabled before fix. (#25135) * Fix:warehouses table name is entrepot which does not hold an fk_soc field, so _checkAccessToResource returns always false and must be disabled before fix. * Fix:warehouses table name is entrepot which does not hold an fk_soc field, so _checkAccessToResource returns always false and must be disabled before fix. * Fix check params It is sufficient to fix check parameters to get it working * Update api_warehouses.class.php --------- Co-authored-by: Laurent Destailleur --- htdocs/product/stock/class/api_warehouses.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/product/stock/class/api_warehouses.class.php b/htdocs/product/stock/class/api_warehouses.class.php index eb6a03c7d45e2..fcaf40826e323 100644 --- a/htdocs/product/stock/class/api_warehouses.class.php +++ b/htdocs/product/stock/class/api_warehouses.class.php @@ -71,7 +71,7 @@ public function get($id) throw new RestException(404, 'warehouse not found'); } - if (!DolibarrApi::_checkAccessToResource('warehouse', $this->warehouse->id)) { + if (!DolibarrApi::_checkAccessToResource('stock', $this->warehouse->id, 'entrepot')) { throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); } From a9db2f6dba8429baadeeb3eaca9666ca82176c37 Mon Sep 17 00:00:00 2001 From: Vincent Penel Date: Mon, 9 Mar 2026 00:37:22 +0100 Subject: [PATCH 118/172] Fix ticket categories dictionay (#37434) --- htdocs/core/modules/modTicket.class.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/core/modules/modTicket.class.php b/htdocs/core/modules/modTicket.class.php index 5228cd1ae74e9..08414da0dc6c2 100644 --- a/htdocs/core/modules/modTicket.class.php +++ b/htdocs/core/modules/modTicket.class.php @@ -148,13 +148,13 @@ public function __construct($db) 'tabsql' => array( 'SELECT f.rowid as rowid, f.code, f.pos, f.label, f.active, f.use_default FROM '.MAIN_DB_PREFIX.'c_ticket_type as f', 'SELECT f.rowid as rowid, f.code, f.pos, f.label, f.active, f.use_default FROM '.MAIN_DB_PREFIX.'c_ticket_severity as f', - 'SELECT f.rowid as rowid, f.entity, f.code, f.pos, f.label, f.active, f.use_default, f.public, f.fk_parent FROM '.MAIN_DB_PREFIX.'c_ticket_category as f', + 'SELECT f.rowid as rowid, f.entity, f.code, f.pos, f.label, f.active, f.use_default, f.public, f.fk_parent,entity FROM '.MAIN_DB_PREFIX.'c_ticket_category as f', 'SELECT f.rowid as rowid, f.code, f.pos, f.label, f.active, f.use_default FROM '.MAIN_DB_PREFIX.'c_ticket_resolution as f' ), 'tabsqlsort' => array("pos ASC", "pos ASC", "pos ASC", "pos ASC"), 'tabfield' => array("code,label,pos,use_default", "code,label,pos,use_default", "code,label,pos,use_default,public,fk_parent", "code,label,pos,use_default"), - 'tabfieldvalue' => array("code,label,pos,use_default", "code,label,pos,use_default", "code,label,pos,use_default,public,fk_parent", "code,label,pos,use_default"), - 'tabfieldinsert' => array("code,label,pos,use_default", "code,label,pos,use_default", "entity, code,label,pos,use_default,public,fk_parent", "code,label,pos,use_default"), + 'tabfieldvalue' => array("code,label,pos,use_default", "code,label,pos,use_default", "code,label,pos,use_default,public,fk_parent,entity", "code,label,pos,use_default"), + 'tabfieldinsert' => array("code,label,pos,use_default", "code,label,pos,use_default", "entity, code,label,pos,use_default,public,fk_parent,entity", "code,label,pos,use_default"), 'tabrowid' => array("rowid", "rowid", "rowid", "rowid"), 'tabcond' => array($conf->ticket->enabled, $conf->ticket->enabled, $conf->ticket->enabled, $conf->ticket->enabled && getDolGlobalString('TICKET_ENABLE_RESOLUTION')), 'tabhelp' => array( From 6689f5bb52fff4072947462a46bef967171a925d Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 12 Mar 2026 11:36:05 +0100 Subject: [PATCH 119/172] Test --- .github/workflows/pr-18-testldr.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-18-testldr.yaml b/.github/workflows/pr-18-testldr.yaml index 6babdf86b7662..7d9cd4328646f 100644 --- a/.github/workflows/pr-18-testldr.yaml +++ b/.github/workflows/pr-18-testldr.yaml @@ -46,11 +46,15 @@ jobs: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} prid: ${{ github.event.pull_request.number }} prid2: ${{ github.event.pull_request_target.number }} + prref: ${{ github.event.pull_request.ref }} + prref2: ${{ github.event.pull_request_target.ref }} url: ${{ github.event.pull_request.html_url }} url2: ${{ github.event.pull_request_target.html_url }} run: | echo "env.prid=${{env.prid}}" - echo "env.prid=${{env.prid2}}" + echo "env.prid2=${{env.prid2}}" + echo "env.prref=${{env.prref}}" + echo "env.prref2=${{env.prref2}}" echo "env.url=${{env.url}}" echo "env.url2=${{env.url2}}" gh pr edit "${{env.prid}}" --add-label "Issue for v18 maintenance Team" From 3016301ae817d8f9bea2a92fddc941b669954783 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 12 Mar 2026 11:40:07 +0100 Subject: [PATCH 120/172] Merge branch '18.0' of git@github.com:Dolibarr/dolibarr.git into 18.0 --- .github/workflows/pr-18-testldr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-18-testldr.yaml b/.github/workflows/pr-18-testldr.yaml index 7d9cd4328646f..32abe8e7e8ef9 100644 --- a/.github/workflows/pr-18-testldr.yaml +++ b/.github/workflows/pr-18-testldr.yaml @@ -75,7 +75,7 @@ jobs: echo "reviewers=$(IFS=, ; echo "${FINAL_REVIEWERS[*]}")" >> $GITHUB_OUTPUT - name: Assign reviewers - if: steps.set-reviewers.outputs.reviewers != '' + if: steps.set-reviewers.outputs.reviewers != '' && ${{env.prid}} != '' env: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} prid: ${{ github.event.pull_request.number }} From ea60298eebf97bb111515cfc45a819e95cd332e8 Mon Sep 17 00:00:00 2001 From: lvessiller-opendsi Date: Thu, 12 Mar 2026 13:46:23 +0100 Subject: [PATCH 121/172] FIX show export full documents checkbox on change format in accountancy export (#37468) * FIX show export full documents checkbox on change format in accountancy export * FIX remove unused var --- htdocs/accountancy/bookkeeping/export.php | 35 +++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/htdocs/accountancy/bookkeeping/export.php b/htdocs/accountancy/bookkeeping/export.php index 665111dc39470..9966e665fdf25 100644 --- a/htdocs/accountancy/bookkeeping/export.php +++ b/htdocs/accountancy/bookkeeping/export.php @@ -764,16 +764,35 @@ } // add documents in an archive for accountancy export (Quadratus) - if (getDolGlobalString('ACCOUNTING_EXPORT_MODELCSV') == AccountancyExport::$EXPORT_TYPE_QUADRATUS) { - $form_question['notifiedexportfull'] = array( - 'name' => 'notifiedexportfull', - 'type' => 'checkbox', - 'label' => $langs->trans('NotifiedExportFull'), - 'value' => 'false', - ); - } + $exportTypesWithDocs = array( + AccountancyExport::$EXPORT_TYPE_QUADRATUS, + ); + $form_question['notifiedexportfull'] = array( + 'name' => 'notifiedexportfull', + 'type' => 'checkbox', + 'label' => $langs->trans('NotifiedExportFull'), + 'value' => 'false', + ); $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans("ExportFilteredList").'...', $langs->trans('ConfirmExportFile'), 'export_fileconfirm', $form_question, '', 1, 420, 600); + $formconfirm .= ''; } // Print form confirm From ff204097e328fa4f78bdc07a1f9f35b8642a064f Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 12 Mar 2026 14:14:17 +0100 Subject: [PATCH 122/172] Revert "Fix ticket categories dictionay (#37434)" This reverts commit a9db2f6dba8429baadeeb3eaca9666ca82176c37. --- htdocs/core/modules/modTicket.class.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/core/modules/modTicket.class.php b/htdocs/core/modules/modTicket.class.php index 08414da0dc6c2..5228cd1ae74e9 100644 --- a/htdocs/core/modules/modTicket.class.php +++ b/htdocs/core/modules/modTicket.class.php @@ -148,13 +148,13 @@ public function __construct($db) 'tabsql' => array( 'SELECT f.rowid as rowid, f.code, f.pos, f.label, f.active, f.use_default FROM '.MAIN_DB_PREFIX.'c_ticket_type as f', 'SELECT f.rowid as rowid, f.code, f.pos, f.label, f.active, f.use_default FROM '.MAIN_DB_PREFIX.'c_ticket_severity as f', - 'SELECT f.rowid as rowid, f.entity, f.code, f.pos, f.label, f.active, f.use_default, f.public, f.fk_parent,entity FROM '.MAIN_DB_PREFIX.'c_ticket_category as f', + 'SELECT f.rowid as rowid, f.entity, f.code, f.pos, f.label, f.active, f.use_default, f.public, f.fk_parent FROM '.MAIN_DB_PREFIX.'c_ticket_category as f', 'SELECT f.rowid as rowid, f.code, f.pos, f.label, f.active, f.use_default FROM '.MAIN_DB_PREFIX.'c_ticket_resolution as f' ), 'tabsqlsort' => array("pos ASC", "pos ASC", "pos ASC", "pos ASC"), 'tabfield' => array("code,label,pos,use_default", "code,label,pos,use_default", "code,label,pos,use_default,public,fk_parent", "code,label,pos,use_default"), - 'tabfieldvalue' => array("code,label,pos,use_default", "code,label,pos,use_default", "code,label,pos,use_default,public,fk_parent,entity", "code,label,pos,use_default"), - 'tabfieldinsert' => array("code,label,pos,use_default", "code,label,pos,use_default", "entity, code,label,pos,use_default,public,fk_parent,entity", "code,label,pos,use_default"), + 'tabfieldvalue' => array("code,label,pos,use_default", "code,label,pos,use_default", "code,label,pos,use_default,public,fk_parent", "code,label,pos,use_default"), + 'tabfieldinsert' => array("code,label,pos,use_default", "code,label,pos,use_default", "entity, code,label,pos,use_default,public,fk_parent", "code,label,pos,use_default"), 'tabrowid' => array("rowid", "rowid", "rowid", "rowid"), 'tabcond' => array($conf->ticket->enabled, $conf->ticket->enabled, $conf->ticket->enabled, $conf->ticket->enabled && getDolGlobalString('TICKET_ENABLE_RESOLUTION')), 'tabhelp' => array( From e29b74195866ac97e6571ee6065a782312c838fb Mon Sep 17 00:00:00 2001 From: Maxime Kohlhaas Date: Thu, 12 Mar 2026 14:40:36 +0100 Subject: [PATCH 123/172] Fix : disabled extrafield should not appear on PDF (#37442) --- htdocs/core/class/commondocgenerator.class.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/htdocs/core/class/commondocgenerator.class.php b/htdocs/core/class/commondocgenerator.class.php index 1d7008aae59ca..a333a0b5fc2e1 100644 --- a/htdocs/core/class/commondocgenerator.class.php +++ b/htdocs/core/class/commondocgenerator.class.php @@ -1453,8 +1453,12 @@ public function getExtrafieldsInHtml($object, $outputlangs, $params = array()) foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) { // Enable extrafield ? $enabled = 0; + if (!empty($extrafields->attributes[$object->table_element]['enabled'][$key])) { + $enabled = (int) dol_eval((string) $extrafields->attributes[$object->table_element]['enabled'][$key], 1, 1, '2'); + } + $disableOnEmpty = 0; - if (!empty($extrafields->attributes[$object->table_element]['printable'][$key])) { + if ($enabled && !empty($extrafields->attributes[$object->table_element]['printable'][$key])) { $printable = intval($extrafields->attributes[$object->table_element]['printable'][$key]); if (in_array($printable, $params['printableEnable']) || in_array($printable, $params['printableEnableNotEmpty'])) { $enabled = 1; From f23b1801e5605f52970c24e8ae394f74c6c0a07a Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 12 Mar 2026 21:42:20 +0100 Subject: [PATCH 124/172] Revert "Revert "Fix ticket categories dictionay (#37434)"" This reverts commit ff204097e328fa4f78bdc07a1f9f35b8642a064f. --- htdocs/core/modules/modTicket.class.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/core/modules/modTicket.class.php b/htdocs/core/modules/modTicket.class.php index 5228cd1ae74e9..08414da0dc6c2 100644 --- a/htdocs/core/modules/modTicket.class.php +++ b/htdocs/core/modules/modTicket.class.php @@ -148,13 +148,13 @@ public function __construct($db) 'tabsql' => array( 'SELECT f.rowid as rowid, f.code, f.pos, f.label, f.active, f.use_default FROM '.MAIN_DB_PREFIX.'c_ticket_type as f', 'SELECT f.rowid as rowid, f.code, f.pos, f.label, f.active, f.use_default FROM '.MAIN_DB_PREFIX.'c_ticket_severity as f', - 'SELECT f.rowid as rowid, f.entity, f.code, f.pos, f.label, f.active, f.use_default, f.public, f.fk_parent FROM '.MAIN_DB_PREFIX.'c_ticket_category as f', + 'SELECT f.rowid as rowid, f.entity, f.code, f.pos, f.label, f.active, f.use_default, f.public, f.fk_parent,entity FROM '.MAIN_DB_PREFIX.'c_ticket_category as f', 'SELECT f.rowid as rowid, f.code, f.pos, f.label, f.active, f.use_default FROM '.MAIN_DB_PREFIX.'c_ticket_resolution as f' ), 'tabsqlsort' => array("pos ASC", "pos ASC", "pos ASC", "pos ASC"), 'tabfield' => array("code,label,pos,use_default", "code,label,pos,use_default", "code,label,pos,use_default,public,fk_parent", "code,label,pos,use_default"), - 'tabfieldvalue' => array("code,label,pos,use_default", "code,label,pos,use_default", "code,label,pos,use_default,public,fk_parent", "code,label,pos,use_default"), - 'tabfieldinsert' => array("code,label,pos,use_default", "code,label,pos,use_default", "entity, code,label,pos,use_default,public,fk_parent", "code,label,pos,use_default"), + 'tabfieldvalue' => array("code,label,pos,use_default", "code,label,pos,use_default", "code,label,pos,use_default,public,fk_parent,entity", "code,label,pos,use_default"), + 'tabfieldinsert' => array("code,label,pos,use_default", "code,label,pos,use_default", "entity, code,label,pos,use_default,public,fk_parent,entity", "code,label,pos,use_default"), 'tabrowid' => array("rowid", "rowid", "rowid", "rowid"), 'tabcond' => array($conf->ticket->enabled, $conf->ticket->enabled, $conf->ticket->enabled, $conf->ticket->enabled && getDolGlobalString('TICKET_ENABLE_RESOLUTION')), 'tabhelp' => array( From 1582f1afb9ab9676d902aa7c8704abc953ab878f Mon Sep 17 00:00:00 2001 From: Vincent Maury Date: Thu, 12 Mar 2026 22:10:36 +0000 Subject: [PATCH 125/172] Fix Inventory bugs #33192 and #35207 (#37405) * Fix Inventory bugs #33207 and #33292 : when click on "validate and generate movements" quantities not still recorded are not recorded * Fix Inventory bugs #33207 and #33292 : when click on "validate and generate movements" quantities not still recorded are not recorded * Fix Inventory bugs #33207 and #33292 : when click on "validate and generate movements" quantities not still recorded are not recorded --------- Co-authored-by: vmaury --- htdocs/product/inventory/inventory.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/htdocs/product/inventory/inventory.php b/htdocs/product/inventory/inventory.php index 1bd941aa1fe24..c459f375df96d 100644 --- a/htdocs/product/inventory/inventory.php +++ b/htdocs/product/inventory/inventory.php @@ -43,7 +43,7 @@ $contextpage = GETPOST('contextpage', 'aZ') ?GETPOST('contextpage', 'aZ') : 'inventorycard'; // To manage different context of search $backtopage = GETPOST('backtopage', 'alpha'); $listoffset = GETPOST('listoffset', 'alpha'); -$limit = GETPOST('limit', 'int') > 0 ?GETPOST('limit', 'int') : $conf->liste_limit; +$limit = GETPOST('limit', 'int') > 0 ? GETPOST('limit', 'int') : $conf->liste_limit; $page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int'); if (empty($page) || $page == -1) { $page = 0; @@ -259,7 +259,7 @@ } // Save quantity found during inventory (when we click on Save button on inventory page) - if ($action =='updateinventorylines' && $permissiontoadd) { + if ($action == 'updateinventorylines' && $permissiontoadd) { $sql = 'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,'; $sql .= ' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated'; $sql .= ' FROM '.MAIN_DB_PREFIX.'inventorydet as id'; @@ -1051,7 +1051,7 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,sele if (isModEnabled('productbatch') && $product_static->hasbatch()) { $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->detail_batch[$obj->batch]->qty; } else { - $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->real; + $valuetoshow = !empty($product_static->stock_warehouse[$obj->fk_warehouse]->real) ? $product_static->stock_warehouse[$obj->fk_warehouse]->real : 0; } } print price2num($valuetoshow, 'MS'); @@ -1215,10 +1215,11 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,sele success: function(result){ window.location.href = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page + 1).$paramwithsearch.'"; }}); + return false; }); - $(".paginationprevious:last").click(function(e){ + $(".paginationprevious:last").click(function(e){ var form = $("#formrecord"); var actionURL = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page).$paramwithsearch.'"; $.ajax({ @@ -1228,9 +1229,10 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,sele success: function(result){ window.location.href = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page - 1).$paramwithsearch.'"; }}); - }); + return false; + }); - $("#idbuttonmakemovementandclose").click(function(e){ + $("#idbuttonmakemovementandclose").click(function(e){ var form = $("#formrecord"); var actionURL = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page).$paramwithsearch.'"; $.ajax({ @@ -1240,7 +1242,8 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,sele success: function(result){ window.location.href = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page - 1).$paramwithsearch.'&action=record"; }}); - }); + return false; + }); }); '; @@ -1315,4 +1318,4 @@ function updateTotalValuation() { // End of page llxFooter(); -$db->close(); +$db->close(); \ No newline at end of file From f90e121650c8f75a3e284e74029e84d2822d4c7c Mon Sep 17 00:00:00 2001 From: atm-lucas <121817516+atm-lucasmantegari@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:31:50 +0100 Subject: [PATCH 126/172] You cannot cancel leave if the balance is negative (#37492) --- htdocs/holiday/class/holiday.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/holiday/class/holiday.class.php b/htdocs/holiday/class/holiday.class.php index 3dd8833564172..44e12602ffa59 100644 --- a/htdocs/holiday/class/holiday.class.php +++ b/htdocs/holiday/class/holiday.class.php @@ -988,7 +988,7 @@ public function update($user = null, $notrigger = 0) $checkBalance = getDictionaryValue('c_holiday_types', 'block_if_negative', $this->fk_type); - if ($checkBalance > 0 && $this->statut != self::STATUS_DRAFT) { + if ($checkBalance > 0 && $this->statut != self::STATUS_DRAFT && $this->statut != self::STATUS_CANCELED) { $balance = $this->getCPforUser($this->fk_user, $this->fk_type); if ($balance < 0) { From 2f5a74e34c5e33ed2c46cd1963461678a00b259c Mon Sep 17 00:00:00 2001 From: William Desportes Date: Sun, 15 Mar 2026 12:01:33 +0100 Subject: [PATCH 127/172] Fix User creation API permission check (#37504) Fixes #30465 Bug introduced by 3c9d8bc931799f9cbf7e04d6cedcf1a3bff44ab8 And still present in the change 7b54824d49f53df651860ae91e768d64170e5c6a See: https://github.com/Dolibarr/dolibarr/commit/7b54824d49f53df651860ae91e768d64170e5c6a#diff-64e82e2f84fc5a1af766e17f0e533221c48231c6bbe3f342af899ee6854748fb --- htdocs/user/class/api_users.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/user/class/api_users.class.php b/htdocs/user/class/api_users.class.php index 627e486ee51bd..2adbd3f4058d9 100644 --- a/htdocs/user/class/api_users.class.php +++ b/htdocs/user/class/api_users.class.php @@ -306,7 +306,7 @@ public function getInfo($includepermissions = 0) public function post($request_data = null) { // Check user authorization - if (!DolibarrApiAccess::$user->hasRight('user', 'creer') && empty(DolibarrApiAccess::$user->admin)) { + if (!DolibarrApiAccess::$user->hasRight('user', 'user', 'creer') && empty(DolibarrApiAccess::$user->admin)) { throw new RestException(403, "User creation not allowed for login ".DolibarrApiAccess::$user->login); } From a9d846dfd627a17d4579e1d06c33f9aff7f06493 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 15 Mar 2026 12:22:16 +0100 Subject: [PATCH 128/172] Disable paralle-lint that is no more available on travis (security bug) --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4775c9ba1e7a5..53c5931d50ab3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -278,13 +278,13 @@ script: set -e #parallel-lint --exclude htdocs/includes --blame . # Exclusions are defined in the ruleset.xml file - if [ "$TRAVIS_PHP_VERSION" = "7.4.22" ]; then - parallel-lint -e php --exclude dev/tools/test/namespacemig --exclude htdocs/includes/composer --exclude htdocs/includes/myclabs --exclude htdocs/includes/phpspec --exclude dev/initdata/dbf/includes \ - --exclude htdocs/includes/sabre --exclude htdocs/includes/phpoffice/PhpSpreadsheet --exclude htdocs/includes/sebastian \ - --exclude htdocs/includes/squizlabs/php_codesniffer --exclude htdocs/includes/jakub-onderka --exclude htdocs/includes/php-parallel-lint --exclude htdocs/includes/symfony \ - --exclude htdocs/includes/mike42/escpos-php/example --exclude htdocs/includes/maximebf \ - --exclude htdocs/includes/phpunit/ --exclude htdocs/includes/tecnickcom/tcpdf/include/barcodes --exclude htdocs/includes/webmozart --blame . - fi + #if [ "$TRAVIS_PHP_VERSION" = "7.4.22" ]; then + # parallel-lint -e php --exclude dev/tools/test/namespacemig --exclude htdocs/includes/composer --exclude htdocs/includes/myclabs --exclude htdocs/includes/phpspec --exclude dev/initdata/dbf/includes \ + # --exclude htdocs/includes/sabre --exclude htdocs/includes/phpoffice/PhpSpreadsheet --exclude htdocs/includes/sebastian \ + # --exclude htdocs/includes/squizlabs/php_codesniffer --exclude htdocs/includes/jakub-onderka --exclude htdocs/includes/php-parallel-lint --exclude htdocs/includes/symfony \ + # --exclude htdocs/includes/mike42/escpos-php/example --exclude htdocs/includes/maximebf \ + # --exclude htdocs/includes/phpunit/ --exclude htdocs/includes/tecnickcom/tcpdf/include/barcodes --exclude htdocs/includes/webmozart --blame . + #fi set +e echo From 39f3abec0c1861fbc28a6aab8c0c040cee5be396 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 15 Mar 2026 13:47:49 +0100 Subject: [PATCH 129/172] Try to ignore travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 53c5931d50ab3..d770a6f7e605b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -92,6 +92,7 @@ install: composer -n init composer -n config vendor-dir htdocs/includes composer -n config -g vendor-dir htdocs/includes + composer -n config audit.block-insecure false echo - | From 9e13496975811c546f70cab34f94bbdbeba0d87f Mon Sep 17 00:00:00 2001 From: William Desportes Date: Sun, 15 Mar 2026 14:48:32 +0100 Subject: [PATCH 130/172] Fix User creation API permission check (#37502) Bug introduced by 3c9d8bc931799f9cbf7e04d6cedcf1a3bff44ab8 Co-authored-by: Laurent Destailleur --- htdocs/user/class/api_users.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/user/class/api_users.class.php b/htdocs/user/class/api_users.class.php index 0acfe30d27956..8ae7b1d4b2656 100644 --- a/htdocs/user/class/api_users.class.php +++ b/htdocs/user/class/api_users.class.php @@ -297,7 +297,7 @@ public function getInfo($includepermissions = 0) public function post($request_data = null) { // Check user authorization - if (empty(DolibarrApiAccess::$user->rights->user->creer) && empty(DolibarrApiAccess::$user->admin)) { + if (empty(DolibarrApiAccess::$user->rights->user->user->creer) && empty(DolibarrApiAccess::$user->admin)) { throw new RestException(401, "User creation not allowed for login ".DolibarrApiAccess::$user->login); } From 2605d33dbc4260e823d2aca8f6566bd3486ebec3 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 15 Mar 2026 14:50:13 +0100 Subject: [PATCH 131/172] Restore parallel-lint --- .travis.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index d770a6f7e605b..0003dbf8fc578 100644 --- a/.travis.yml +++ b/.travis.yml @@ -277,15 +277,15 @@ script: echo "Checking PHP syntax errors (only 1 version to not overload travis and avoid duplicate tests)" # Ensure we catch errors set -e - #parallel-lint --exclude htdocs/includes --blame . + parallel-lint --exclude htdocs/includes --blame . # Exclusions are defined in the ruleset.xml file - #if [ "$TRAVIS_PHP_VERSION" = "7.4.22" ]; then - # parallel-lint -e php --exclude dev/tools/test/namespacemig --exclude htdocs/includes/composer --exclude htdocs/includes/myclabs --exclude htdocs/includes/phpspec --exclude dev/initdata/dbf/includes \ - # --exclude htdocs/includes/sabre --exclude htdocs/includes/phpoffice/PhpSpreadsheet --exclude htdocs/includes/sebastian \ - # --exclude htdocs/includes/squizlabs/php_codesniffer --exclude htdocs/includes/jakub-onderka --exclude htdocs/includes/php-parallel-lint --exclude htdocs/includes/symfony \ - # --exclude htdocs/includes/mike42/escpos-php/example --exclude htdocs/includes/maximebf \ - # --exclude htdocs/includes/phpunit/ --exclude htdocs/includes/tecnickcom/tcpdf/include/barcodes --exclude htdocs/includes/webmozart --blame . - #fi + if [ "$TRAVIS_PHP_VERSION" = "7.4.22" ]; then + parallel-lint -e php --exclude dev/tools/test/namespacemig --exclude htdocs/includes/composer --exclude htdocs/includes/myclabs --exclude htdocs/includes/phpspec --exclude dev/initdata/dbf/includes \ + --exclude htdocs/includes/sabre --exclude htdocs/includes/phpoffice/PhpSpreadsheet --exclude htdocs/includes/sebastian \ + --exclude htdocs/includes/squizlabs/php_codesniffer --exclude htdocs/includes/jakub-onderka --exclude htdocs/includes/php-parallel-lint --exclude htdocs/includes/symfony \ + --exclude htdocs/includes/mike42/escpos-php/example --exclude htdocs/includes/maximebf \ + --exclude htdocs/includes/phpunit/ --exclude htdocs/includes/tecnickcom/tcpdf/include/barcodes --exclude htdocs/includes/webmozart --blame . + fi set +e echo From 88b66741fcc1639a9c9ebcac8cc0ce984471a77a Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 15 Mar 2026 14:52:07 +0100 Subject: [PATCH 132/172] Merge branch '14.0' of git@github.com:Dolibarr/dolibarr.git into 15.0 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3b25d9e6a3588..1665e88195a12 100644 --- a/.travis.yml +++ b/.travis.yml @@ -273,7 +273,7 @@ script: echo "Checking PHP syntax errors (only 1 version to not overload travis and avoid duplicate tests)" # Ensure we catch errors set -e - parallel-lint --exclude htdocs/includes --blame . + #parallel-lint --exclude htdocs/includes --blame . # Exclusions are defined in the ruleset.xml file if [ "$TRAVIS_PHP_VERSION" = "7.4.22" ]; then parallel-lint -e php --exclude dev/tools/test/namespacemig --exclude htdocs/includes/composer --exclude htdocs/includes/myclabs --exclude htdocs/includes/phpspec --exclude dev/initdata/dbf/includes \ From 209323598a80242fcd0ff27a0d6c540c8ef5923d Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 15 Mar 2026 14:52:53 +0100 Subject: [PATCH 133/172] Restore parallel-lint only on last version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0003dbf8fc578..651922f2b5960 100644 --- a/.travis.yml +++ b/.travis.yml @@ -277,7 +277,7 @@ script: echo "Checking PHP syntax errors (only 1 version to not overload travis and avoid duplicate tests)" # Ensure we catch errors set -e - parallel-lint --exclude htdocs/includes --blame . + #parallel-lint --exclude htdocs/includes --blame . # Exclusions are defined in the ruleset.xml file if [ "$TRAVIS_PHP_VERSION" = "7.4.22" ]; then parallel-lint -e php --exclude dev/tools/test/namespacemig --exclude htdocs/includes/composer --exclude htdocs/includes/myclabs --exclude htdocs/includes/phpspec --exclude dev/initdata/dbf/includes \ From c19e3d9690f8f7921d619604e8bfdcbc6ac59c8d Mon Sep 17 00:00:00 2001 From: Eric - CAP-REL <1468823+rycks@users.noreply.github.com> Date: Thu, 19 Mar 2026 09:39:53 +0100 Subject: [PATCH 134/172] php version of perl package builder historical script (#37545) --- build/makepack-dolibarr.php | 1394 +++++++++++++++++++++++++++++++++++ 1 file changed, 1394 insertions(+) create mode 100755 build/makepack-dolibarr.php diff --git a/build/makepack-dolibarr.php b/build/makepack-dolibarr.php new file mode 100755 index 0000000000000..dc6d5d70e039e --- /dev/null +++ b/build/makepack-dolibarr.php @@ -0,0 +1,1394 @@ +#!/usr/bin/env php + + * + * PHP CLI rewrite of makepack-dolibarr.pl + * + * Environment variables you can set to have generated packages moved into a specific dir: + * DESTIBETARC='/media/HDDATA1_LD/Mes Sites/Web/Dolibarr/dolibarr.org/files/lastbuild' + * DESTISTABLE='/media/HDDATA1_LD/Mes Sites/Web/Dolibarr/dolibarr.org/files/stable' + * DESTIMODULES='/media/HDDATA1_LD/Mes Sites/Web/Admin1/wwwroot/files/modules' + */ + +// ============================================================================ +// Helper functions +// ============================================================================ + +/** + * Return ANSI-colored text + * + * @param string $text Text to colorize + * @param string $color Color name (red, green, yellow, blue, magenta, cyan, white) + * @return string Colored text with reset suffix + */ +function colorText(string $text, string $color): string +{ + $colors = [ + 'reset' => "\033[0m", + 'red' => "\033[31m", + 'green' => "\033[32m", + 'yellow' => "\033[33m", + 'blue' => "\033[34m", + 'magenta' => "\033[35m", + 'cyan' => "\033[36m", + 'white' => "\033[37m", + ]; + $code = $colors[$color] ?? $colors['reset']; + return $code . $text . "\033[0m"; +} + +/** + * Execute a shell command and return stdout + * + * @param string $cmd Shell command to execute + * @return string Command output + */ +function run(string $cmd): string +{ + return shell_exec($cmd) ?? ''; +} + +/** + * Read a line from STDIN + * + * @param string $message Prompt message to display + * @return string User input (trimmed) + */ +function prompt(string $message = ''): string +{ + if ($message !== '') { + echo $message; + } + return trim(fgets(STDIN) ?: ''); +} + + +// ============================================================================ +// Configuration +// ============================================================================ + +// Change this to defined target for option 98 and 99 +$PROJECT = 'dolibarr'; +$PUBLISHBETARC = (getenv('DESTIASSOLOGIN') ?: '') . '@vmprod1.dolibarr.org:/home/dolibarr/asso.dolibarr.org/dolibarr_documents/website/www.dolibarr.org/files'; +$PUBLISHSTABLE = (getenv('DESTISFLOGIN') ?: '') . '@frs.sourceforge.net:/home/frs/project/dolibarr'; + +// Due to implicit origin on git commands +$GITREMOTENAME = getenv('GITREMOTENAME') ?: ''; + +$LISTETARGET = ['TGZ', 'ZIP', 'RPM_GENERIC', 'RPM_FEDORA', 'RPM_MANDRIVA', 'RPM_OPENSUSE', 'DEB', 'EXEDOLIWAMP', 'SNAPSHOT']; + +$REQUIREMENTPUBLISH = [ + 'SF' => 'git ssh rsync', + 'ASSO' => 'git ssh rsync', +]; + +$REQUIREMENTTARGET = [ + 'TGZ' => 'tar', + 'ZIP' => '7z', + 'XZ' => 'xz', + 'RPM_GENERIC' => 'rpmbuild', + 'RPM_FEDORA' => 'rpmbuild', + 'RPM_MANDRIVA' => 'rpmbuild', + 'RPM_OPENSUSE' => 'rpmbuild', + 'DEB' => 'dpkg', + 'FLATPACK' => 'flatpack', + 'EXEDOLIWAMP' => 'ISCC.exe', + 'SNAPSHOT' => 'tar', +]; + +$ALTERNATEPATH = [ + '7z' => '7-ZIP', + 'makensis.exe' => 'NSIS', +]; + +$RPMSUBVERSION = 'auto'; +$RPMDIR = ''; +if (is_dir('/usr/src/redhat')) { $RPMDIR = '/usr/src/redhat'; } // redhat +if (is_dir('/usr/src/packages')) { $RPMDIR = '/usr/src/packages'; } // opensuse +if (is_dir('/usr/src/RPM')) { $RPMDIR = '/usr/src/RPM'; } // mandrake + +$VERSION = '4.0'; + + +// ============================================================================ +// MAIN +// ============================================================================ + +// Detect script directory and name +$scriptPath = realpath($argv[0]) ?: $argv[0]; +$DIR = dirname($scriptPath); +$PROG = pathinfo($scriptPath, PATHINFO_FILENAME); +$Extension = pathinfo($scriptPath, PATHINFO_EXTENSION); +$SOURCE = dirname($DIR); +$DESTI = $SOURCE . '/build'; + +if ($SOURCE[0] !== '/' && !preg_match('/^[a-z]:/i', $SOURCE)) { + echo "Error: Launch the script $PROG.$Extension with its full path from /.\n"; + echo "$PROG.$Extension aborted.\n"; + sleep(2); + exit(1); +} + +// Check environment variables +$ENVDESTIBETARC = getenv('DESTIBETARC') ?: ''; +$ENVDESTISTABLE = getenv('DESTISTABLE') ?: ''; + +if (!$ENVDESTIBETARC || !$ENVDESTISTABLE) { + echo "Error: Missing environment variables.\n"; + echo "You must define the environment variable DESTIBETARC and DESTISTABLE to point to the\ndirectories where you want to save the generated packages.\n"; + echo "$PROG.$Extension aborted.\n"; + echo "\n"; + echo "You can set them with\n"; + echo "On Linux:\n"; + echo "export DESTIBETARC='/tmp'; export DESTISTABLE='/tmp';\n"; + echo "On Windows:\n"; + echo "set DESTIBETARC=c:/tmp\n"; + echo "set DESTISTABLE=c:/tmp\n"; + echo "\n"; + echo "Example: DESTIBETARC='/media/HDDATA1_LD/Mes Sites/Web/Dolibarr/dolibarr.org/files/lastbuild'\n"; + echo "Example: DESTISTABLE='/media/HDDATA1_LD/Mes Sites/Web/Dolibarr/dolibarr.org/files/stable'\n"; + sleep(2); + exit(1); +} + +if (!is_dir($ENVDESTIBETARC) || !is_dir($ENVDESTISTABLE)) { + echo "Error: Directory of environment variable DESTIBETARC ($ENVDESTIBETARC) or DESTISTABLE ($ENVDESTISTABLE) does not exist.\n"; + echo "$PROG.$Extension aborted.\n"; + sleep(2); + exit(1); +} + +if (!$GITREMOTENAME) { + echo "Error: environment variable GITREMOTENAME does not exist. You can set it to 'origin' or any other git remote name.\n"; + echo "$PROG.$Extension aborted.\n"; + sleep(2); + exit(1); +} + +// Detect OS type +$OS = ''; +$CR = ''; +$PROGPATH = ''; + +if (stripos(PHP_OS, 'linux') !== false || (is_dir('/etc') && is_dir('/var') && stripos(PHP_OS, 'cygwin') === false)) { + $OS = 'linux'; + $CR = ''; +} elseif (is_dir('/etc') && is_dir('/Users')) { + $OS = 'macosx'; + $CR = ''; +} elseif (stripos(PHP_OS, 'cygwin') !== false || stripos(PHP_OS, 'win') !== false || stripos(PHP_OS, 'msys') !== false) { + $OS = 'windows'; + $CR = "\r"; +} + +if (!$OS) { + echo "Error: Can't detect your OS.\n"; + echo "Can't continue.\n"; + echo "$PROG.$Extension aborted.\n"; + sleep(2); + exit(1); +} + +// Define buildroot +$TEMP = ''; +if ($OS === 'linux' || $OS === 'macosx') { + $TEMP = getenv('TEMP') ?: (getenv('TMP') ?: '/tmp'); +} +if ($OS === 'windows') { + $TEMP = getenv('TEMP') ?: (getenv('TMP') ?: 'c:/temp'); + $PROGPATH = getenv('ProgramFiles') ?: ''; +} + +if (!$TEMP || !is_dir($TEMP)) { + echo "Error: A temporary directory can not be find.\n"; + echo "Check that TEMP or TMP environment variable is set correctly.\n"; + echo "$PROG.$Extension aborted.\n"; + sleep(2); + exit(2); +} + +$BUILDROOT = $TEMP . '/buildroot'; + + +// Get version $MAJOR, $MINOR and $BUILD +$filefuncPath = $SOURCE . '/htdocs/filefunc.inc.php'; +$filefuncContent = file_get_contents($filefuncPath); +if ($filefuncContent === false) { + echo "Error: Can't open descriptor file $filefuncPath\n"; + exit(1); +} + +$PROJVERSION = ''; +if (preg_match("/define\('DOL_VERSION',\s*'([\d\.a-z\-]+)'\)/i", $filefuncContent, $matches)) { + $PROJVERSION = $matches[1]; +} + +$versionParts = explode('.', $PROJVERSION, 3); +$MAJOR = $versionParts[0] ?? ''; +$MINOR = $versionParts[1] ?? ''; +$BUILD = $versionParts[2] ?? ''; + +if ($MINOR === '') { + echo "Error can't detect version into $filefuncPath\n"; + exit(1); +} + + +// Set vars for packaging +$FILENAME = $PROJECT; +$FILENAMESNAPSHOT = "$PROJECT-snapshot"; +$FILENAMETGZ = "$PROJECT-$MAJOR.$MINOR.$BUILD"; +$FILENAMEZIP = "$PROJECT-$MAJOR.$MINOR.$BUILD"; +$FILENAMEXZ = "$PROJECT-$MAJOR.$MINOR.$BUILD"; +$FILENAMEDEB = 'see later'; +$FILENAMEEXEDOLIWAMP = "DoliWamp-$MAJOR.$MINOR.$BUILD"; + +// For RPM +$ARCH = 'noarch'; +$newbuild = $BUILD; +$newbuild = preg_replace('/(dev|alpha)/i', '0.1.a', $newbuild); // dev (fedora) +$newbuild = preg_replace('/beta(.?)/i', '0.2.beta', $newbuild); // beta (fedora) +$newbuild = preg_replace('/rc(.?)/i', '0.3.rc', $newbuild); // rc (fedora) +if (strpos($newbuild, '-') === false) { + $newbuild .= '-0.4'; // finale (fedora) +} +$REL1 = preg_replace('/-.*$/', '', $newbuild); +if ($RPMSUBVERSION === 'auto') { + $RPMSUBVERSION = preg_replace('/^.*-/', '', $newbuild); +} +$FILENAMETGZ2 = "$PROJECT-$MAJOR.$MINOR.$REL1"; +$FILENAMERPM = $FILENAMETGZ2 . '-' . $RPMSUBVERSION . '.' . $ARCH . '.rpm'; +$FILENAMERPMSRC = $FILENAMETGZ2 . '-' . $RPMSUBVERSION . '.src.rpm'; + +// For Deb +$newbuild = $BUILD; +$newbuild = preg_replace('/(dev|alpha)/i', '1', $newbuild); // dev +$newbuild = preg_replace('/beta(.?)/i', '2', $newbuild); // beta +$newbuild = preg_replace('/rc(.?)/i', '3', $newbuild); // rc +if (strpos($newbuild, '-') === false) { + $newbuild .= '-4'; // finale +} +// now newbuild is 0-1 or 0-4 for example +$build = preg_replace('/-.*$/', '', $newbuild); +// now build is 0 for example +$FILENAMEDEBNATIVE = "{$PROJECT}_{$MAJOR}.{$MINOR}.{$build}"; +$FILENAMEDEB = "{$PROJECT}_{$MAJOR}.{$MINOR}.{$newbuild}"; +$FILENAMEDEBSHORT = "{$PROJECT}_{$MAJOR}.{$MINOR}.{$build}"; + + +// Parse command line arguments +$copyalreadydone = 0; +$batch = 0; +$target = ''; +$PREFIX = ''; + +for ($i = 1; $i < $argc; $i++) { + if (preg_match('/^-*target=(\w+)/i', $argv[$i], $m)) { + $target = $m[1]; + $batch = 1; + } + if (preg_match('/^-*desti=(.+)/i', $argv[$i], $m)) { + $DESTI = $m[1]; + } + if (preg_match('/^-*prefix=(.+)/i', $argv[$i], $m)) { + $PREFIX = $m[1]; + $FILENAMESNAPSHOT .= '-' . $PREFIX; + } +} + +// Force output dir if env vars are defined +if ($ENVDESTIBETARC && preg_match('/[a-z]/i', $BUILD)) { + $DESTI = $ENVDESTIBETARC; +} +if ($ENVDESTISTABLE && preg_match('/^[0-9]+$/', $BUILD)) { + $DESTI = $ENVDESTISTABLE; +} + +// Force target site for publishing if env vars are defined +$envPublishBetarc = getenv('PUBLISHBETARC') ?: ''; +$envPublishStable = getenv('PUBLISHSTABLE') ?: ''; +if ($envPublishBetarc && preg_match('/[a-z]/i', $BUILD)) { + $PUBLISHBETARC = $envPublishBetarc; +} +if ($envPublishStable && preg_match('/^[0-9]+$/', $BUILD)) { + $PUBLISHSTABLE = $envPublishStable; +} + +echo "Makepack version $VERSION\n"; +echo "Building/publishing package name: $PROJECT\n"; +echo "Building/publishing package version: $MAJOR.$MINOR.$BUILD\n"; +echo "Source directory (SOURCE): $SOURCE\n"; +echo "Target directory (DESTI) : $DESTI\n"; + + +// ============================================================================ +// Choose package targets +// ============================================================================ + +$CHOOSEDTARGET = []; +$CHOOSEDPUBLISH = []; + +if ($target) { + $targetUpper = strtoupper($target); + if ($targetUpper === 'ALL') { + foreach ($LISTETARGET as $key) { + if ($key !== 'SNAPSHOT' && $key !== 'SF' && $key !== 'ASSO') { + $CHOOSEDTARGET[$key] = 1; + } + } + } + if ($targetUpper !== 'ALL' && $targetUpper !== 'SF' && $targetUpper !== 'ASSO') { + $CHOOSEDTARGET[$targetUpper] = 1; + } + if ($targetUpper === 'SF') { + $CHOOSEDPUBLISH['SF'] = 1; + } + if ($targetUpper === 'ASSO') { + $CHOOSEDPUBLISH['ASSO'] = 1; + } +} else { + $found = false; + $NUM_SCRIPT = ''; + + while (!$found) { + $cpt = 0; + printf(" %2d - %-14s (%s)\n", $cpt, 'ALL (1..10)', 'Need ' . implode(',', array_values($REQUIREMENTTARGET))); + $cpt++; + printf(" %2d - %-14s\n", $cpt, 'Generate check file'); + foreach ($LISTETARGET as $tgt) { + $cpt++; + printf(" %2d - %-14s (%s)\n", $cpt, $tgt, 'Need ' . $REQUIREMENTTARGET[$tgt]); + } + $cpt = 98; + printf(" %2d - %-14s (%s)\n", $cpt, 'ASSO (publish)', 'Need ' . $REQUIREMENTPUBLISH['ASSO']); + $cpt = 99; + printf(" %2d - %-14s (%s)\n", $cpt, 'SF (publish)', 'Need ' . $REQUIREMENTPUBLISH['SF']); + + $NUM_SCRIPT = prompt("Choose one target number or several separated with space (0 - $cpt): "); + + if (!preg_match('/^[0-9\s]+$/', $NUM_SCRIPT)) { + echo "This is not a valid package number list.\n"; + } else { + $found = true; + } + } + + echo "\n"; + + if ($NUM_SCRIPT === '98') { + $CHOOSEDPUBLISH['ASSO'] = 1; + } elseif ($NUM_SCRIPT === '99') { + $CHOOSEDPUBLISH['SF'] = 1; + } elseif ($NUM_SCRIPT === '0') { + $CHOOSEDTARGET['-CHKSUM'] = 1; + foreach ($LISTETARGET as $key) { + if ($key !== 'SNAPSHOT' && $key !== 'ASSO' && $key !== 'SF') { + $CHOOSEDTARGET[$key] = 1; + } + } + } elseif ($NUM_SCRIPT === '1') { + $CHOOSEDTARGET['-CHKSUM'] = 1; + } else { + foreach (preg_split('/\s+/', $NUM_SCRIPT, -1, PREG_SPLIT_NO_EMPTY) as $num) { + $idx = (int) $num - 2; + if (isset($LISTETARGET[$idx])) { + $CHOOSEDTARGET[$LISTETARGET[$idx]] = 1; + } + } + } +} + + +// ============================================================================ +// Test if requirements are ok +// ============================================================================ + +$atleastonerpm = 0; +ksort($CHOOSEDTARGET); + +foreach ($CHOOSEDTARGET as $tgt => $val) { + if (preg_match('/RPM/i', $tgt)) { + if ($atleastonerpm && $DESTI === "$SOURCE/build") { + echo "Error: You asked creation of several rpms. Because all rpm have same name, you must defined an environment variable DESTI to tell packager where it can create subdirs for each generated package.\n"; + exit(1); + } + $atleastonerpm = 1; + } + + if (!isset($REQUIREMENTTARGET[$tgt])) { + continue; + } + + foreach (preg_split('/[,\s]+/', $REQUIREMENTTARGET[$tgt], -1, PREG_SPLIT_NO_EMPTY) as $req) { + echo "Test requirement for target $tgt: Search '$req'... "; + + $newreq = $req; + $newparam = ''; + if ($newreq === 'zip') { $newparam .= '-h'; } + if ($newreq === 'xz') { $newparam .= '-h'; } + + $cmd = "\"$newreq\" $newparam 2>&1"; + echo "Test command $cmd... "; + + $outputLines = []; + exec($cmd, $outputLines, $coderetour); + $ret = implode("\n", $outputLines); + + if ($coderetour !== 0 && (($coderetour === 1 && $OS === 'windows' && !preg_match('/Usage/i', $ret)) || ($coderetour === 127 && $OS !== 'windows')) && $PROGPATH) { + // Not found, try in PROGPATH + $altPath = $ALTERNATEPATH[$req] ?? ''; + $outputLines = []; + exec("\"$PROGPATH/$altPath/$req\" 2>&1", $outputLines, $coderetour); + $ret = implode("\n", $outputLines); + $REQUIREMENTTARGET[$tgt] = "$PROGPATH/$altPath/$req"; + } + + if ($coderetour !== 0 && (($coderetour === 1 && $OS === 'windows' && !preg_match('/Usage/i', $ret)) || ($coderetour === 127 && $OS !== 'windows'))) { + // Not found error + echo "Not found\nCan't build target $tgt. Requirement '$req' not found in PATH\n"; + $CHOOSEDTARGET[$tgt] = -1; + break; + } else { + echo " Found $req\n"; + } + } +} + +echo "\n"; + + +// ============================================================================ +// Check if there is at least one target to build +// ============================================================================ + +$nboftargetok = 0; +$nboftargetneedbuildroot = 0; +$nbofpublishneedtag = 0; +$nbofpublishneedchangelog = 0; + +ksort($CHOOSEDTARGET); +foreach ($CHOOSEDTARGET as $tgt => $val) { + if ($tgt === '-CHKSUM') { $nbofpublishneedchangelog++; } + if ($val < 0) { continue; } + if ($tgt !== 'EXE' && $tgt !== 'EXEDOLIWAMP' && $tgt !== '-CHKSUM') { + $nboftargetneedbuildroot++; + } + $nboftargetok++; +} + +ksort($CHOOSEDPUBLISH); +foreach ($CHOOSEDPUBLISH as $tgt => $val) { + if ($val < 0) { continue; } + if ($tgt === 'ASSO') { $nbofpublishneedchangelog++; } + if ($tgt === 'SF') { $nbofpublishneedchangelog++; $nbofpublishneedtag++; } + $nboftargetok++; +} + + +if ($nboftargetok) { + // ======================================================================== + // Check Changelog + // ======================================================================== + + if ($nbofpublishneedchangelog) { + $TMPBUILDTOCHECKCHANGELOG = preg_replace('/\-rc\d*/', '', $BUILD); + $TMPBUILDTOCHECKCHANGELOG = preg_replace('/\-beta\d*/', '', $TMPBUILDTOCHECKCHANGELOG); + + echo "\nCheck if ChangeLog is ok for version $MAJOR.$MINOR.$TMPBUILDTOCHECKCHANGELOG\n"; + $ret = run("grep \"ChangeLog for $MAJOR.$MINOR.$TMPBUILDTOCHECKCHANGELOG\" \"$SOURCE/ChangeLog\" 2>&1"); + + if (!trim($ret)) { + echo colorText("Error: The ChangeLogFile was not updated. Run the following command before building package for $MAJOR.$MINOR.$BUILD:\n", 'yellow'); + } else { + echo "ChangeLog for $MAJOR.$MINOR.$BUILD was found into '$SOURCE/ChangeLog'. But you can regenerate it with command:\n"; + } + + if (!$BUILD || $BUILD === '0-rc') { + // For a major version + echo 'cd ~/git/dolibarr_' . $MAJOR . '.' . $MINOR . '; git log `git rev-list --boundary ' . $MAJOR . '.' . $MINOR . '..origin/develop | grep ^- | cut -c2- | head -n 1`.. --no-merges --pretty=short --oneline | sed -e "s/^[0-9a-z]* //" | grep -e \'^FIX\|NEW\|CLOSE\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\' | sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' > /tmp/aaa'; + } else { + // For a maintenance release + echo 'cd ~/git/dolibarr_' . $MAJOR . '.' . $MINOR . '; git log ' . $MAJOR . '.' . $MINOR . '.' . ($BUILD - 1) . '.. | grep -v "Merge branch" | grep -v "Merge pull" | grep "^ " | sed -e "s/^[0-9a-z]* *//" | grep -e \'^FIX\|NEW\|CLOSE\' | sort -u | sed \'s/FIXED:/FIX:/g\' | sed \'s/FIXED :/FIX:/g\' | sed \'s/FIX :/FIX:/g\' | sed \'s/FIX /FIX: /g\' | sed \'s/CLOSE/NEW/g\' | sed \'s/NEW :/NEW:/g\' | sed \'s/NEW /NEW: /g\' > /tmp/aaa'; + } + echo "\n"; + + if (!trim($ret)) { + $WAITKEY = prompt("\nPress F to force and continue anyway (or other key to stop)... "); + if ($WAITKEY !== 'F') { + echo "Canceled.\n"; + exit(0); + } + } + } + + + // ======================================================================== + // Build xml check file + // ======================================================================== + + if (isset($CHOOSEDTARGET['-CHKSUM']) && $CHOOSEDTARGET['-CHKSUM'] > 0) { + echo "Go to directory $SOURCE\n"; + $olddir = getcwd(); + chdir($SOURCE); + + echo "Clean $SOURCE/htdocs/includes/autoload.php\n"; + run("rm -f $SOURCE/htdocs/includes/autoload.php"); + + $ret = run("git ls-files . --exclude-standard --others"); + if (trim($ret)) { + echo "Some files exists in source directory and are not indexed neither excluded in .gitignore.\n"; + echo $ret; + echo "Canceled.\n"; + exit(0); + } + + echo "Create xml check file with md5 checksum with command php $SOURCE/build/generate_filelist_xml.php release=$MAJOR.$MINOR.$BUILD\n"; + $outputLines = []; + exec("php $SOURCE/build/generate_filelist_xml.php release=$MAJOR.$MINOR.$BUILD", $outputLines, $retcode); + $ret = implode("\n", $outputLines); + if ($retcode !== 0) { + echo "Error running generate_filelist_xml.php please check\n"; + echo $ret; + echo "Canceled.\n"; + exit(0); + } + echo $ret . "\n"; + + // Copy to final dir + $NEWDESTI = $DESTI; + if (!is_dir("$NEWDESTI/signatures")) { + mkdir("$NEWDESTI/signatures", 0777, true); + } + echo "Copy \"$SOURCE/htdocs/install/filelist-$MAJOR.$MINOR.$BUILD.xml\" to $NEWDESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml\n"; + copy("$SOURCE/htdocs/install/filelist-$MAJOR.$MINOR.$BUILD.xml", "$NEWDESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml"); + } + + + // ======================================================================== + // Update GIT tag if required + // ======================================================================== + + if ($nbofpublishneedtag) { + echo "Go to directory $SOURCE\n"; + $olddir = getcwd(); + chdir($SOURCE); + + echo 'Run git tag -a -m "' . $MAJOR . '.' . $MINOR . '.' . $BUILD . '" "' . $MAJOR . '.' . $MINOR . '.' . $BUILD . '"' . "\n"; + $ret = run("git tag -a -m \"$MAJOR.$MINOR.$BUILD\" \"$MAJOR.$MINOR.$BUILD\" 2>&1"); + + if (preg_match('/(already exists|existe déjà)/', $ret)) { + $QUESTIONOVERWRITETAG = prompt("WARNING: Tag $MAJOR.$MINOR.$BUILD already exists. Overwrite (y/N) ? "); + if (preg_match('/[oy]/i', $QUESTIONOVERWRITETAG)) { + echo 'Run git tag -a -f -m "' . $MAJOR . '.' . $MINOR . '.' . $BUILD . '" "' . $MAJOR . '.' . $MINOR . '.' . $BUILD . '"' . "\n"; + run("git tag -a -f -m \"$MAJOR.$MINOR.$BUILD\" \"$MAJOR.$MINOR.$BUILD\""); + echo "Run git push $GITREMOTENAME -f --tags\n"; + run("git push $GITREMOTENAME -f --tags"); + } + } else { + echo "Run git push $GITREMOTENAME --tags\n"; + run("git push $GITREMOTENAME --tags"); + } + + chdir($olddir); + } + + + // ======================================================================== + // Update buildroot if required + // ======================================================================== + + if ($nboftargetneedbuildroot) { + if (!$copyalreadydone) { + echo "Creation of a buildroot used for all packages\n"; + + echo "Delete directory $BUILDROOT\n"; + run("rm -fr \"$BUILDROOT\""); + + @mkdir($BUILDROOT, 0777, true); + @mkdir("$BUILDROOT/$PROJECT", 0777, true); + echo "Copy $SOURCE into $BUILDROOT/$PROJECT\n"; + run("cp -pr \"$SOURCE\" \"$BUILDROOT/$PROJECT\""); + } + + echo "Clean $BUILDROOT\n"; + run("rm -f $BUILDROOT/$PROJECT/.buildpath"); + run("rm -fr $BUILDROOT/$PROJECT/.cache"); + run("rm -fr $BUILDROOT/$PROJECT/.codeclimate"); + run("rm -fr $BUILDROOT/$PROJECT/.externalToolBuilders"); + run("rm -fr $BUILDROOT/$PROJECT/.git*"); + run("rm -fr $BUILDROOT/$PROJECT/.project"); + run("rm -fr $BUILDROOT/$PROJECT/.pydevproject"); + run("rm -fr $BUILDROOT/$PROJECT/.settings"); + run("rm -fr $BUILDROOT/$PROJECT/.scrutinizer.yml"); + run("rm -fr $BUILDROOT/$PROJECT/.stickler.yml"); + run("rm -fr $BUILDROOT/$PROJECT/.travis.yml"); + run("rm -fr $BUILDROOT/$PROJECT/.tx"); + run("rm -f $BUILDROOT/$PROJECT/build.xml"); + run("rm -f $BUILDROOT/$PROJECT/phpstan.neon"); + run("rm -f $BUILDROOT/$PROJECT/pom.xml"); + run("rm -f $BUILDROOT/$PROJECT/README-*.md"); + + run("rm -fr $BUILDROOT/$PROJECT/build/html"); + run("rm -f $BUILDROOT/$PROJECT/build/Doli*-*"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr_*.deb"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr_*.dsc"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr_*.tar.gz"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr_*.tar.xz"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.deb"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.rpm"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.tar"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.tar.gz"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.tar.xz"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.tgz"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.xz"); + run("rm -f $BUILDROOT/$PROJECT/build/dolibarr-*.zip"); + run("rm -f $BUILDROOT/$PROJECT/build/doxygen/doxygen_warnings.log"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/cache.manifest"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.mysql"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.nova*"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.old"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf.php.pgsql"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/conf/conf*sav*"); + + run("rm -f $BUILDROOT/$PROJECT/htdocs/install/mssql/README"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/install/mysql/README"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/install/pgsql/README"); + + run("rm -fr $BUILDROOT/$PROJECT/htdocs/install/mssql"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/install/sqlite3"); + + run("rm -fr $BUILDROOT/$PROJECT/node_modules"); + + run("rm -fr $BUILDROOT/$PROJECT/dev/ansible"); + run("rm -fr $BUILDROOT/$PROJECT/dev/codesniffer"); + run("rm -fr $BUILDROOT/$PROJECT/dev/codetemplates"); + run("rm -fr $BUILDROOT/$PROJECT/dev/examples/ldap"); + run("rm -fr $BUILDROOT/$PROJECT/dev/examples/zapier"); + run("rm -fr $BUILDROOT/$PROJECT/dev/initdata"); + run("rm -fr $BUILDROOT/$PROJECT/dev/initdemo"); + run("rm -fr $BUILDROOT/$PROJECT/dev/resources/dbmodel"); + run("rm -fr $BUILDROOT/$PROJECT/dev/resources/iso-normes"); + run("rm -fr $BUILDROOT/$PROJECT/dev/resources/licence"); + run("rm -fr $BUILDROOT/$PROJECT/dev/mail"); + run("rm -fr $BUILDROOT/$PROJECT/dev/multitail"); + run("rm -fr $BUILDROOT/$PROJECT/dev/phpcheckstyle"); + run("rm -fr $BUILDROOT/$PROJECT/dev/phpunit"); + run("rm -fr $BUILDROOT/$PROJECT/dev/security"); + run("rm -fr $BUILDROOT/$PROJECT/dev/spec"); + run("rm -fr $BUILDROOT/$PROJECT/dev/test"); + run("rm -fr $BUILDROOT/$PROJECT/dev/uml"); + run("rm -fr $BUILDROOT/$PROJECT/dev/vagrant"); + run("rm -fr $BUILDROOT/$PROJECT/dev/xdebug"); + run("rm -f $BUILDROOT/$PROJECT/dev/dolibarr_changes.txt"); + run("rm -f $BUILDROOT/$PROJECT/dev/README"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot2.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot3.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot4.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot5.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot6.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot7.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot8.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot9.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot10.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot11.png"); + run("rm -f $BUILDROOT/$PROJECT/doc/images/dolibarr_screenshot12.png"); + + // Security to avoid to package data files + echo "Remove documents dir\n"; + run("rm -fr $BUILDROOT/$PROJECT/document"); + run("rm -fr $BUILDROOT/$PROJECT/documents"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/document"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/documents"); + + echo "Remove subdir of custom dir\n"; + echo "find $BUILDROOT/$PROJECT/htdocs/custom/* -type d -exec rm -fr {} \\;\n"; + run("find $BUILDROOT/$PROJECT/htdocs/custom/* -type d -exec rm -fr {} \\; >/dev/null 2>&1"); + echo "find $BUILDROOT/$PROJECT/htdocs/custom/* -type l -exec rm -fr {} \\;\n"; + run("find $BUILDROOT/$PROJECT/htdocs/custom/* -type l -exec rm -fr {} \\; >/dev/null 2>&1"); + + // Remove known external modules to avoid any error when packaging from env where external modules are tested + run("rm -fr $BUILDROOT/$PROJECT/htdocs/abricot*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/accountingexport*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/allscreens*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/ancotec*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/cabinetmed*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/calling*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/bootstrap*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/dolimed*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/dolimod*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/factory*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/forceproject*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/lead*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/management*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/multicompany*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/ndf*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/nltechno*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/nomenclature*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/of/"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/oscim*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/pos*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/teclib*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/timesheet*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/webmail*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/theme/common/fontawesome-5/svgs"); + + // Remove other test files + run("rm -fr $BUILDROOT/$PROJECT/htdocs/public/test"); + run("rm -fr $BUILDROOT/$PROJECT/test"); + run("rm -fr $BUILDROOT/$PROJECT/Thumbs.db $BUILDROOT/$PROJECT/*/Thumbs.db $BUILDROOT/$PROJECT/*/*/Thumbs.db $BUILDROOT/$PROJECT/*/*/*/Thumbs.db $BUILDROOT/$PROJECT/*/*/*/*/Thumbs.db"); + run("rm -f $BUILDROOT/$PROJECT/.cvsignore $BUILDROOT/$PROJECT/*/.cvsignore $BUILDROOT/$PROJECT/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/*/*/.cvsignore $BUILDROOT/$PROJECT/*/*/*/*/*/*/.cvsignore"); + run("rm -f $BUILDROOT/$PROJECT/.gitignore $BUILDROOT/$PROJECT/*/.gitignore $BUILDROOT/$PROJECT/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/*/*/.gitignore $BUILDROOT/$PROJECT/*/*/*/*/*/*/.gitignore"); + + // Remove files installed by composer + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/geoip/sample*.*"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/bin"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/ckeditor/ckeditor/adapters"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/ckeditor/ckeditor/samples"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/ckeditor/_source"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/composer"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/doctrine"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/jquery/plugins/multiselect/MIT-LICENSE.txt"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/jquery/plugins/select2/release.sh"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/mike42/escpos-php/doc"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/mike42/escpos-php/example"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/mike42/escpos-php/test"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/mobiledetect/mobiledetectlib/.gitmodules"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/mobiledetect/mobiledetectlib/docs"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/.github"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/docs"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/samples"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/scripts"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/src"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nnnick/chartjs/test"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/nusoap/samples"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/php-iban/docs"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/sabre/sabre/*/tests"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/stripe/tests"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/stripe/LICENSE"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/examples"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/dejavu-fonts-ttf-*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/freefont-*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/ae_fonts_*"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/fonts/utils"); + run("rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tecnickcom/tcpdf/tools"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/vendor"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/webmozart"); + run("rm -f $BUILDROOT/$PROJECT/htdocs/includes/autoload.php"); + } + + + // ======================================================================== + // Build package for each target + // ======================================================================== + + ksort($CHOOSEDTARGET); + foreach ($CHOOSEDTARGET as $tgt => $val) { + if ($val < 0) { continue; } + if ($tgt === '-CHKSUM') { continue; } + + echo "\nBuild package for target $tgt\n"; + + + // --- SNAPSHOT --- + if ($tgt === 'SNAPSHOT') { + $NEWDESTI = $DESTI; + + echo "Remove target $FILENAMESNAPSHOT.tgz...\n"; + @unlink("$NEWDESTI/$FILENAMESNAPSHOT.tgz"); + + run("rm -fr $BUILDROOT/$FILENAMESNAPSHOT"); + echo "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$FILENAMESNAPSHOT\n"; + $cmd = "cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$FILENAMESNAPSHOT\""; + run($cmd); + + echo "Compress $BUILDROOT into $FILENAMESNAPSHOT.tgz...\n"; + $cmd = "tar --exclude doli*.tgz --exclude doli*.deb --exclude doli*.exe --exclude doli*.xz --exclude doli*.zip --exclude doli*.rpm --exclude .cache --exclude .settings --exclude conf.php --exclude conf.php.mysql --exclude conf.php.old --exclude conf.php.postgres --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$FILENAMESNAPSHOT.tgz\" $FILENAMESNAPSHOT"; + echo $cmd . "\n"; + run($cmd); + + // Move to final dir + echo "Move $FILENAMESNAPSHOT.tgz to $NEWDESTI/$FILENAMESNAPSHOT.tgz\n"; + run("mv \"$FILENAMESNAPSHOT.tgz\" \"$NEWDESTI/$FILENAMESNAPSHOT.tgz\""); + continue; + } + + + // --- TGZ --- + if ($tgt === 'TGZ') { + $NEWDESTI = $DESTI; + if (preg_match('/stable/', $NEWDESTI)) { + @mkdir($DESTI . '/standard'); + if (is_dir($DESTI . '/standard')) { $NEWDESTI = $DESTI . '/standard'; } + } + + echo "Remove target $FILENAMETGZ.tgz...\n"; + @unlink("$NEWDESTI/$FILENAMETGZ.tgz"); + + run("rm -fr $BUILDROOT/$FILENAMETGZ"); + echo "Copy $BUILDROOT/$PROJECT/ to $BUILDROOT/$FILENAMETGZ\n"; + $cmd = "cp -pr \"$BUILDROOT/$PROJECT/\" \"$BUILDROOT/$FILENAMETGZ\""; + run($cmd); + + run("rm -fr $BUILDROOT/$FILENAMETGZ/build/exe"); + run("rm -fr $BUILDROOT/$FILENAMETGZ/htdocs/includes/ckeditor/_source"); + + echo "Compress $FILENAMETGZ into $FILENAMETGZ.tgz...\n"; + $cmd = "tar --exclude-vcs --exclude-from \"$BUILDROOT/$PROJECT/build/tgz/tar_exclude.txt\" --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$BUILDROOT/$FILENAMETGZ.tgz\" $FILENAMETGZ"; + echo "$cmd\n"; + run($cmd); + + // Move to final dir + echo "Move $BUILDROOT/$FILENAMETGZ.tgz to $NEWDESTI/$FILENAMETGZ.tgz\n"; + run("mv \"$BUILDROOT/$FILENAMETGZ.tgz\" \"$NEWDESTI/$FILENAMETGZ.tgz\""); + continue; + } + + + // --- XZ --- + if ($tgt === 'XZ') { + $NEWDESTI = $DESTI; + if (preg_match('/stable/', $NEWDESTI)) { + @mkdir($DESTI . '/standard'); + if (is_dir($DESTI . '/standard')) { $NEWDESTI = $DESTI . '/standard'; } + } + + echo "Remove target $FILENAMEXZ.xz...\n"; + @unlink("$NEWDESTI/$FILENAMEXZ.xz"); + + run("rm -fr $BUILDROOT/$FILENAMEXZ"); + echo "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$FILENAMEXZ\n"; + $cmd = "cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$FILENAMEXZ\""; + run($cmd); + + run("rm -fr $BUILDROOT/$FILENAMEXZ/build/exe"); + run("rm -fr $BUILDROOT/$FILENAMEXZ/htdocs/includes/ckeditor/_source"); + + echo "Compress $FILENAMEXZ into $FILENAMEXZ.xz...\n"; + + echo "Go to directory $BUILDROOT\n"; + $olddir = getcwd(); + chdir($BUILDROOT); + $cmd = "xz -9 -r $BUILDROOT/$FILENAMEXZ.xz *"; + echo $cmd . "\n"; + run($cmd); + chdir($olddir); + + // Move to final dir + echo "Move $FILENAMEXZ.xz to $NEWDESTI/$FILENAMEXZ.xz\n"; + run("mv \"$BUILDROOT/$FILENAMEXZ.xz\" \"$NEWDESTI/$FILENAMEXZ.xz\""); + continue; + } + + + // --- ZIP --- + if ($tgt === 'ZIP') { + $NEWDESTI = $DESTI; + if (preg_match('/stable/', $NEWDESTI)) { + @mkdir($DESTI . '/standard'); + if (is_dir($DESTI . '/standard')) { $NEWDESTI = $DESTI . '/standard'; } + } + + echo "Remove target $FILENAMEZIP.zip...\n"; + @unlink("$NEWDESTI/$FILENAMEZIP.zip"); + + run("rm -fr $BUILDROOT/$FILENAMEZIP"); + echo "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$FILENAMEZIP\n"; + $cmd = "cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$FILENAMEZIP\""; + run($cmd); + + run("rm -fr $BUILDROOT/$FILENAMEZIP/build/exe"); + run("rm -fr $BUILDROOT/$FILENAMEZIP/htdocs/includes/ckeditor/_source"); + + echo "Compress $FILENAMEZIP into $FILENAMEZIP.zip...\n"; + + echo "Go to directory $BUILDROOT\n"; + $olddir = getcwd(); + chdir($BUILDROOT); + $cmd = "7z a -r -tzip -xr@\"$BUILDROOT/$FILENAMEZIP/build/zip/zip_exclude.txt\" -mx $BUILDROOT/$FILENAMEZIP.zip $FILENAMEZIP/*"; + echo $cmd . "\n"; + run($cmd); + chdir($olddir); + + // Move to final dir + echo "Move $FILENAMEZIP.zip to $NEWDESTI/$FILENAMEZIP.zip\n"; + run("mv \"$BUILDROOT/$FILENAMEZIP.zip\" \"$NEWDESTI/$FILENAMEZIP.zip\""); + continue; + } + + + // --- RPM --- + if (preg_match('/RPM/', $tgt)) { + $NEWDESTI = $DESTI; + $subdir = 'package_rpm_generic'; + if (preg_match('/FEDO/i', $tgt)) { $subdir = 'package_rpm_redhat-fedora'; } + if (preg_match('/MAND/i', $tgt)) { $subdir = 'package_rpm_mandriva'; } + if (preg_match('/OPEN/i', $tgt)) { $subdir = 'package_rpm_opensuse'; } + if (preg_match('/stable/', $NEWDESTI)) { + @mkdir($DESTI . '/' . $subdir); + if (is_dir($DESTI . '/' . $subdir)) { $NEWDESTI = $DESTI . '/' . $subdir; } + } + + if ($RPMDIR === '') { $RPMDIR = (getenv('HOME') ?: '') . '/rpmbuild'; } + + echo "Version is $MAJOR.$MINOR.$REL1-$RPMSUBVERSION\n"; + + echo "Remove target $FILENAMERPM...\n"; + @unlink("$NEWDESTI/$FILENAMERPM"); + echo "Remove target $FILENAMERPMSRC...\n"; + @unlink("$NEWDESTI/$FILENAMERPMSRC"); + + echo "Create directory $BUILDROOT/$FILENAMETGZ2\n"; + run("rm -fr $BUILDROOT/$FILENAMETGZ2"); + + echo "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$FILENAMETGZ2\n"; + $cmd = "cp -pr '$BUILDROOT/$PROJECT' '$BUILDROOT/$FILENAMETGZ2'"; + run($cmd); + + echo "Set permissions on files/dir\n"; + run("chmod -R 755 $BUILDROOT/$FILENAMETGZ2"); + $cmd = "find $BUILDROOT/$FILENAMETGZ2 -type f -exec chmod 644 {} \\; "; + run($cmd); + + // Build tgz + echo "Compress $FILENAMETGZ2 into $FILENAMETGZ2.tgz...\n"; + run("tar --exclude-from \"$SOURCE/build/tgz/tar_exclude.txt\" --directory \"$BUILDROOT\" -czvf \"$BUILDROOT/$FILENAMETGZ2.tgz\" $FILENAMETGZ2"); + + if (!is_dir($RPMDIR . '/SOURCES')) { @mkdir($RPMDIR . '/SOURCES', 0777, true); } + echo "Move $BUILDROOT/$FILENAMETGZ2.tgz to $RPMDIR/SOURCES/$FILENAMETGZ2.tgz\n"; + $cmd = "mv $BUILDROOT/$FILENAMETGZ2.tgz $RPMDIR/SOURCES/$FILENAMETGZ2.tgz"; + run($cmd); + + $BUILDFIC = $FILENAME . '.spec'; + $BUILDFICSRC = $FILENAME . '_generic.spec'; + if (preg_match('/FEDO/i', $tgt)) { $BUILDFICSRC = $FILENAME . '_fedora.spec'; } + if (preg_match('/MAND/i', $tgt)) { $BUILDFICSRC = $FILENAME . '_mandriva.spec'; } + if (preg_match('/OPEN/i', $tgt)) { $BUILDFICSRC = $FILENAME . '_opensuse.spec'; } + + $day = str_pad(date('j'), 2, ' ', STR_PAD_LEFT); + $datestring = date('D M ') . $day . date(' Y'); + $changelogstring = "* $datestring Laurent Destailleur (eldy) $MAJOR.$MINOR.$REL1-$RPMSUBVERSION\n- Upstream release\n"; + + echo "Generate file $BUILDROOT/$BUILDFIC from $SOURCE/build/rpm/$BUILDFICSRC\n"; + $specContent = file_get_contents("$SOURCE/build/rpm/$BUILDFICSRC"); + if ($specContent === false) { + echo "Error: Can't read $SOURCE/build/rpm/$BUILDFICSRC\n"; + exit(1); + } + $specContent = str_replace('__FILENAMETGZ__', $FILENAMETGZ, $specContent); + $specContent = str_replace('__VERSION__', "$MAJOR.$MINOR.$REL1", $specContent); + $specContent = str_replace('__RELEASE__', $RPMSUBVERSION, $specContent); + $specContent = str_replace('__CHANGELOGSTRING__', $changelogstring, $specContent); + file_put_contents("$BUILDROOT/$BUILDFIC", $specContent); + + echo "Copy patch file to $RPMDIR/SOURCES\n"; + run("cp \"$SOURCE/build/rpm/dolibarr-forrpm.patch\" \"$RPMDIR/SOURCES\""); + run("chmod 644 $RPMDIR/SOURCES/dolibarr-forrpm.patch"); + + echo "Launch RPM build (rpmbuild --clean -ba $BUILDROOT/$BUILDFIC)\n"; + run("rpmbuild --clean -ba $BUILDROOT/$BUILDFIC"); + + // Move to final dir + echo "Move $RPMDIR/RPMS/$ARCH/{$FILENAMETGZ2}-{$RPMSUBVERSION}*.{$ARCH}.rpm into $NEWDESTI/{$FILENAMETGZ2}-{$RPMSUBVERSION}*.{$ARCH}.rpm\n"; + $cmd = "mv $RPMDIR/RPMS/$ARCH/{$FILENAMETGZ2}-{$RPMSUBVERSION}*.{$ARCH}.rpm \"$NEWDESTI/\""; + run($cmd); + echo "Move $RPMDIR/SRPMS/{$FILENAMETGZ2}-{$RPMSUBVERSION}*.src.rpm into $NEWDESTI/{$FILENAMETGZ2}-{$RPMSUBVERSION}*.src.rpm\n"; + $cmd = "mv $RPMDIR/SRPMS/{$FILENAMETGZ2}-{$RPMSUBVERSION}*.src.rpm \"$NEWDESTI/\""; + run($cmd); + echo "Move $RPMDIR/SOURCES/$FILENAMETGZ2.tgz into $NEWDESTI/$FILENAMETGZ2.tgz\n"; + // This line was commented out in original: $cmd="mv ..."; $ret=`$cmd`; + continue; + } + + + // --- DEB --- + if ($tgt === 'DEB') { + $NEWDESTI = $DESTI; + if (preg_match('/stable/', $NEWDESTI)) { + @mkdir($DESTI . '/package_debian-ubuntu'); + if (is_dir($DESTI . '/package_debian-ubuntu')) { $NEWDESTI = $DESTI . '/package_debian-ubuntu'; } + } + + $olddir = getcwd(); + + echo "Remove target ${FILENAMEDEB}_all.deb...\n"; + @unlink("$NEWDESTI/${FILENAMEDEB}_all.deb"); + echo "Remove target ${FILENAMEDEB}.dsc...\n"; + @unlink("$NEWDESTI/${FILENAMEDEB}.dsc"); + echo "Remove target ${FILENAMEDEB}.tar.gz...\n"; + @unlink("$NEWDESTI/${FILENAMEDEB}.tar.gz"); + echo "Remove target ${FILENAMEDEB}.changes...\n"; + @unlink("$NEWDESTI/${FILENAMEDEB}.changes"); + echo "Remove target ${FILENAMEDEB}.debian.tar.gz...\n"; + @unlink("$NEWDESTI/${FILENAMEDEB}.debian.tar.gz"); + echo "Remove target ${FILENAMEDEB}.debian.tar.xz...\n"; + @unlink("$NEWDESTI/${FILENAMEDEB}.debian.tar.xz"); + echo "Remove target ${FILENAMEDEBNATIVE}.orig.tar.gz...\n"; + @unlink("$NEWDESTI/${FILENAMEDEBNATIVE}.orig.tar.gz"); + + run("rm -fr $BUILDROOT/$PROJECT.tmp"); + run("rm -fr $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build"); + + echo "Copy $BUILDROOT/$PROJECT to $BUILDROOT/$PROJECT.tmp\n"; + $cmd = "cp -pr \"$BUILDROOT/$PROJECT\" \"$BUILDROOT/$PROJECT.tmp\""; + run($cmd); + $cmd = "cp -pr \"$BUILDROOT/$PROJECT/build/debian/apache/.htaccess\" \"$BUILDROOT/$PROJECT.tmp/build/debian/apache/.htaccess\""; + run($cmd); + + echo "Remove other files\n"; + run("rm -f $BUILDROOT/$PROJECT.tmp/README-FR.md"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/README"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/README-FR"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/aps"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/dmg"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/pad/README"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/tgz/README"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/debian/po"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/debian/source"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/changelog"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/compat"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/control*"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/copyright"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.config"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.desktop"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.docs"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.install"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.lintian-overrides"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.postrm"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.postinst"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.templates"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/dolibarr.templates.futur"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/rules"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/README.Debian"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/README.howto"); + run("rm -f $BUILDROOT/$PROJECT.tmp/build/debian/watch"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/doap"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/exe"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/launchpad"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/live"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/patch"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/perl"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/rpm"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/build/zip"); + // Remove duplicate license files + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/_source/LICENSE.md"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/_source/plugins/scayt/LICENSE.md"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/_source/plugins/wsc/LICENSE.md"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/LICENSE.md"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/plugins/scayt/LICENSE.md"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/ckeditor/ckeditor/plugins/wsc/LICENSE.md"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/php-iban/LICENSE"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/flot/LICENSE.txt"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/ColReorder/License.txt"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/ColVis/License.txt"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/FixedColumns/License.txt"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/extensions/Responsive/License.txt"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/datatables/license.txt"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/jquery/plugins/select2/LICENSE"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/mike42/escpos-php/LICENSE.md"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/htdocs/includes/mobiledetect/mobiledetectlib/LICENSE.txt"); + + // Remove files we don't need (already removed) + run("rm -fr $BUILDROOT/$PROJECT.tmp/.codeclimate.yml"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/.pre-commit-config.yaml"); + run("rm -fr $BUILDROOT/$PROJECT.tmp/.vscode"); + run("find $BUILDROOT/$PROJECT.tmp/ -type f -name '.editorconfig' -exec rm {} \\;"); + run("find $BUILDROOT/$PROJECT.tmp/ -type f -name '.travis.yml' -exec rm {} \\;"); + + // Rename upstream changelog to match debian rules + run("mv $BUILDROOT/$PROJECT.tmp/ChangeLog $BUILDROOT/$PROJECT.tmp/changelog"); + + // Prepare source package (init debian dir) + echo "Create directory $BUILDROOT/$PROJECT.tmp/debian\n"; + run("mkdir \"$BUILDROOT/$PROJECT.tmp/debian\""); + + echo "Copy $SOURCE/build/debian/xxx to $BUILDROOT/$PROJECT.tmp/debian\n"; + + // Add files for dpkg-source (changelog) + $changelogContent = file_get_contents("$SOURCE/build/debian/changelog"); + if ($changelogContent === false) { + echo "Error: Can't read $SOURCE/build/debian/changelog\n"; + exit(1); + } + $changelogContent = str_replace('__VERSION__', "$MAJOR.$MINOR.$newbuild", $changelogContent); + file_put_contents("$BUILDROOT/$PROJECT.tmp/debian/changelog", $changelogContent); + + // Add files for dpkg-source + run("cp -f \"$SOURCE/build/debian/compat\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/control\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/copyright\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.desktop\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.docs\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.install\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.lintian-overrides\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.xpm\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/rules\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/watch\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -fr \"$SOURCE/build/debian/patches\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -fr \"$SOURCE/build/debian/po\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -fr \"$SOURCE/build/debian/source\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -fr \"$SOURCE/build/debian/apache\" \"$BUILDROOT/$PROJECT.tmp/debian/apache\""); + run("cp -f \"$SOURCE/build/debian/apache/.htaccess\" \"$BUILDROOT/$PROJECT.tmp/debian/apache\""); + run("cp -fr \"$SOURCE/build/debian/lighttpd\" \"$BUILDROOT/$PROJECT.tmp/debian/lighttpd\""); + // Add files also required to build binary package + run("cp -f \"$SOURCE/build/debian/dolibarr.config\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.postinst\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.postrm\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/dolibarr.templates\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + run("cp -f \"$SOURCE/build/debian/install.forced.php.install\" \"$BUILDROOT/$PROJECT.tmp/debian\""); + + echo "Set permissions on files/dir\n"; + run("chmod -R 755 $BUILDROOT/$PROJECT.tmp"); + run("find $BUILDROOT/$PROJECT.tmp -type f -exec chmod 644 {} \\; "); + run("find $BUILDROOT/$PROJECT.tmp/build -name '*.php' -type f -exec chmod 755 {} \\; "); + run("find $BUILDROOT/$PROJECT.tmp/build -name '*.dpatch' -type f -exec chmod 755 {} \\; "); + run("find $BUILDROOT/$PROJECT.tmp/build -name '*.pl' -type f -exec chmod 755 {} \\; "); + run("find $BUILDROOT/$PROJECT.tmp/dev -name '*.php' -type f -exec chmod 755 {} \\; "); + run("chmod 755 $BUILDROOT/$PROJECT.tmp/debian/rules"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/dev/translation/autotranslator.class.php"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/class/actions_mymodule.class.php"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/class/api_mymodule.class.php"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/class/myobject.class.php"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/core/modules/modMyModule.class.php"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/mymoduleindex.php"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/myobject_card.php"); + run("chmod -R 644 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/myobject_list.php"); + run("chmod -R 755 $BUILDROOT/$PROJECT.tmp/htdocs/modulebuilder/template/scripts/mymodule.php"); + run("find $BUILDROOT/$PROJECT.tmp/scripts -name '*.php' -type f -exec chmod 755 {} \\; "); + run("find $BUILDROOT/$PROJECT.tmp/scripts -name '*.sh' -type f -exec chmod 755 {} \\; "); + + echo "Rename directory $BUILDROOT/$PROJECT.tmp into $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build\n"; + $cmd = "mv $BUILDROOT/$PROJECT.tmp $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build"; + run($cmd); + + echo "Go into directory $BUILDROOT\n"; + chdir($BUILDROOT); + + // We need a tarball to be able to build "quilt" debian package + echo "Compress $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build into $BUILDROOT/$FILENAMEDEBNATIVE.orig.tar.gz...\n"; + $cmd = "tar --exclude-vcs --exclude-from \"$BUILDROOT/$PROJECT/build/tgz/tar_exclude.txt\" --directory \"$BUILDROOT\" --mode=go-w --group=500 --owner=500 -czvf \"$BUILDROOT/$FILENAMEDEBNATIVE.orig.tar.gz\" $PROJECT-$MAJOR.$MINOR.$build"; + echo $cmd . "\n"; + run($cmd); + + // Creation of source package + echo "Go into directory $BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build\n"; + chdir("$BUILDROOT/$PROJECT-$MAJOR.$MINOR.$build"); + $cmd = "dpkg-buildpackage -us -uc --compression=gzip"; + echo "Launch DEB build ($cmd)\n"; + $ret = run("$cmd 2>&1"); + echo $ret . "\n"; + + chdir($olddir); + + echo "You can check bin package with lintian --pedantic -E -I \"$NEWDESTI/${FILENAMEDEB}_all.deb\"\n"; + echo "You can check src package with lintian --pedantic -E -I \"$NEWDESTI/${FILENAMEDEB}.dsc\"\n"; + + // Move to final dir + echo "Move *_all.deb *.dsc *.orig.tar.gz *.changes to $NEWDESTI\n"; + run("mv $BUILDROOT/*_all.deb \"$NEWDESTI/\""); + run("mv $BUILDROOT/*.dsc \"$NEWDESTI/\""); + run("mv $BUILDROOT/*.orig.tar.gz \"$NEWDESTI/\""); + run("mv $BUILDROOT/*.debian.tar.gz \"$NEWDESTI/\""); + run("mv $BUILDROOT/*.changes \"$NEWDESTI/\""); + continue; + } + + + // --- EXEDOLIWAMP --- + if ($tgt === 'EXEDOLIWAMP') { + $NEWDESTI = $DESTI; + if (preg_match('/stable/', $NEWDESTI)) { + @mkdir($DESTI . '/package_windows'); + if (is_dir($DESTI . '/package_windows')) { $NEWDESTI = $DESTI . '/package_windows'; } + } + + echo "Remove target $NEWDESTI/$FILENAMEEXEDOLIWAMP.exe...\n"; + @unlink("$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe"); + + if ($OS === 'windows') { + echo "Check that ISCC.exe is in your PATH.\n"; + } else { + echo "Check that in your Wine setup, you have created a Z: drive that point to your / directory.\n"; + } + + $SOURCEBACK = str_replace('/', '\\', $SOURCE); + + echo "Prepare file \"$SOURCEBACK\\build\\exe\\doliwamp\\doliwamp.tmp.iss\" from \"$SOURCEBACK\\build\\exe\\doliwamp\\doliwamp.iss\"\n"; + + $issContent = file_get_contents("$SOURCE/build/exe/doliwamp/doliwamp.iss"); + if ($issContent === false) { + echo "Error: Can't read $SOURCE/build/exe/doliwamp/doliwamp.iss\n"; + exit(1); + } + $issContent = str_replace('__FILENAMEEXEDOLIWAMP__', $FILENAMEEXEDOLIWAMP, $issContent); + file_put_contents("$SOURCE/build/exe/doliwamp/doliwamp.tmp.iss", $issContent); + + echo "Compil exe $FILENAMEEXEDOLIWAMP.exe file from iss file \"$SOURCEBACK\\build\\exe\\doliwamp\\doliwamp.tmp.iss\" on OS $OS\n"; + + $cmd = ''; + if ($OS === 'windows') { + $cmd = "ISCC.exe \"$SOURCEBACK\\build\\exe\\doliwamp\\doliwamp.tmp.iss\""; + } + if ($cmd) { + echo "$cmd\n"; + $ret = run($cmd); + echo "ret=$ret\n"; + } + + // Move to final dir + echo "Move \"$SOURCE/build/$FILENAMEEXEDOLIWAMP.exe\" to $NEWDESTI/$FILENAMEEXEDOLIWAMP.exe\n"; + @rename("$SOURCE/build/$FILENAMEEXEDOLIWAMP.exe", "$NEWDESTI/$FILENAMEEXEDOLIWAMP.exe"); + + echo "Remove tmp file $SOURCE/build/exe/doliwamp/doliwamp.tmp.iss\n"; + @unlink("$SOURCE/build/exe/doliwamp/doliwamp.tmp.iss"); + + continue; + } + } + + + // ======================================================================== + // Publish package for each target + // ======================================================================== + + ksort($CHOOSEDPUBLISH); + foreach ($CHOOSEDPUBLISH as $tgt => $val) { + if ($val < 0) { continue; } + + echo "\nList of files to publish (BUILD=$BUILD)\n"; + + if ($tgt === 'ASSO' && preg_match('/[a-z]/i', $BUILD)) { + // Not stable + $filestoscansf = [ + "$DESTI/$FILENAMERPM" => 'Dolibarr installer for Fedora-Redhat-Mandriva-Opensuse (DoliRpm)', + "$DESTI/${FILENAMEDEB}_all.deb" => 'Dolibarr installer for Debian-Ubuntu (DoliDeb)', + "$DESTI/$FILENAMEEXEDOLIWAMP.exe" => 'Dolibarr installer for Windows (DoliWamp)', + "$DESTI/$FILENAMETGZ.tgz" => 'Dolibarr ERP-CRM', + "$DESTI/$FILENAMETGZ.zip" => 'Dolibarr ERP-CRM', + ]; + $filestoscanstableasso = [ + "$DESTI/$FILENAMERPM" => '', + "$DESTI/${FILENAMEDEB}_all.deb" => '', + "$DESTI/$FILENAMEEXEDOLIWAMP.exe" => '', + "$DESTI/$FILENAMETGZ.tgz" => '', + "$DESTI/$FILENAMETGZ.zip" => '', + ]; + } else { + $filestoscansf = [ + "$DESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml" => 'none', + "$DESTI/package_rpm_generic/$FILENAMERPM" => 'Dolibarr installer for Fedora-Redhat-Mandriva-Opensuse (DoliRpm)', + "$DESTI/package_rpm_generic/$FILENAMERPMSRC" => 'none', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}_all.deb" => 'Dolibarr installer for Debian-Ubuntu (DoliDeb)', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}_amd64.changes" => 'none', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}.dsc" => 'none', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}.debian.tar.gz" => 'none', + "$DESTI/package_debian-ubuntu/${FILENAMEDEBSHORT}.orig.tar.gz" => 'none', + "$DESTI/package_windows/$FILENAMEEXEDOLIWAMP.exe" => 'Dolibarr installer for Windows (DoliWamp)', + "$DESTI/standard/$FILENAMETGZ.tgz" => 'Dolibarr ERP-CRM', + "$DESTI/standard/$FILENAMETGZ.zip" => 'Dolibarr ERP-CRM', + ]; + $filestoscanstableasso = [ + "$DESTI/signatures/filelist-$MAJOR.$MINOR.$BUILD.xml" => 'signatures', + "$DESTI/package_rpm_generic/$FILENAMERPM" => 'package_rpm_generic', + "$DESTI/package_rpm_generic/$FILENAMERPMSRC" => 'package_rpm_generic', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}_all.deb" => 'package_debian-ubuntu', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}_amd64.changes" => 'package_debian-ubuntu', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}.dsc" => 'package_debian-ubuntu', + "$DESTI/package_debian-ubuntu/${FILENAMEDEB}.debian.tar.gz" => 'package_debian-ubuntu', + "$DESTI/package_debian-ubuntu/${FILENAMEDEBSHORT}.orig.tar.gz" => 'package_debian-ubuntu', + "$DESTI/package_windows/$FILENAMEEXEDOLIWAMP.exe" => 'package_windows', + "$DESTI/standard/$FILENAMETGZ.tgz" => 'standard', + "$DESTI/standard/$FILENAMETGZ.zip" => 'standard', + ]; + } + + ksort($filestoscansf); + foreach ($filestoscansf as $file => $label) { + $filesize = @filesize($file); + $filedate = @filemtime($file); + echo $file . ' ' . ($filesize ? '(found)' : '(not found)'); + if ($filesize) { echo ' - ' . $filesize; } + if ($filedate) { echo ' - ' . date('Y-m-d H:i:s', $filedate); } + echo "\n"; + } + + if ($tgt === 'SF' || $tgt === 'ASSO') { + echo "\n"; + + $PUBLISH = ''; + if ($tgt === 'SF') { $PUBLISH = $PUBLISHSTABLE; } + if ($tgt === 'ASSO' && preg_match('/[a-z]/i', $BUILD)) { $PUBLISH = $PUBLISHBETARC . '/lastbuild'; } + if ($tgt === 'ASSO' && preg_match('/^[0-9]+$/', $BUILD)) { $PUBLISH = $PUBLISHBETARC . '/stable'; } + + $NEWPUBLISH = $PUBLISH; + prompt("Publish to target $NEWPUBLISH. Click enter or CTRL+C...\n"); + + echo "Create empty dir /tmp/emptydir. We need it to create target dir using rsync.\n"; + run("mkdir -p \"/tmp/emptydir/\""); + + $filestoscan = $filestoscansf; + ksort($filestoscan); + + foreach ($filestoscan as $file => $label) { + $filesize = @filesize($file); + if (!$filesize) { continue; } + + if ($tgt === 'SF') { + if ($label === 'none') { + continue; + } + $destFolder = "$NEWPUBLISH/$label/$MAJOR.$MINOR.$BUILD"; + } elseif ($tgt === 'ASSO' && preg_match('/stable/', $NEWPUBLISH)) { + $destFolder = "$NEWPUBLISH/" . ($filestoscanstableasso[$file] ?? ''); + } elseif ($tgt === 'ASSO' && !preg_match('/stable/', $NEWPUBLISH)) { + $destFolder = $NEWPUBLISH; + } else { + // No more used + $dirnameonly = preg_replace('/.*\/([^\/]+)\/[^\/]+$/', '$1', $file); + $destFolder = "$NEWPUBLISH/$dirnameonly"; + } + + echo "\n"; + echo "Publish file $file to $destFolder\n"; + + $command = "rsync -s -e 'ssh' --recursive /tmp/emptydir/ \"$destFolder\""; + echo "$command\n"; + run($command); + + $command = "rsync -s -e 'ssh' \"$file\" \"$destFolder\""; + echo "$command\n"; + $ret = run($command); + echo "$ret\n"; + } + } + } +} + + +// ============================================================================ +// Summary +// ============================================================================ + +echo "\n----- Summary -----\n"; + +ksort($CHOOSEDTARGET); +foreach ($CHOOSEDTARGET as $tgt => $val) { + if ($tgt === '-CHKSUM') { + echo "Checksum was generated\n"; + continue; + } + if ($val < 0) { + echo "Package $tgt not built (bad requirement).\n"; + } else { + echo "Package $tgt built successfully in $DESTI\n"; + } +} + +if (!$batch) { + prompt("\nPress key to finish..."); +} + +exit(0); From 5a9c9e5c4618f10632ddff31c45aeab568fd63e4 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 20 Mar 2026 16:15:49 +0100 Subject: [PATCH 135/172] CI --- htdocs/comm/card.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/comm/card.php b/htdocs/comm/card.php index 794c0851be67e..df35900a1a5bf 100644 --- a/htdocs/comm/card.php +++ b/htdocs/comm/card.php @@ -1099,7 +1099,7 @@ * Latest shipments */ if (isModEnabled("shipping") && $user->hasRight('expedition', 'lire')) { - $param = 'entity=' . ((int) $objp->entity); + $param = ''; $sql = 'SELECT e.rowid as id'; $sql .= ', e.ref, e.entity, e.fk_projet'; From fd874dcc04555ea74b2b3c6c655e7950038d4a7f Mon Sep 17 00:00:00 2001 From: Maxime Kohlhaas Date: Fri, 20 Mar 2026 16:28:38 +0100 Subject: [PATCH 136/172] FIX : product ref was not printed on supplier recurring invoice (#37535) --- htdocs/fourn/class/fournisseur.facture-rec.class.php | 9 +++++++-- htdocs/fourn/facture/card-rec.php | 8 -------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/htdocs/fourn/class/fournisseur.facture-rec.class.php b/htdocs/fourn/class/fournisseur.facture-rec.class.php index 558ce11478436..d1014df7d1b10 100644 --- a/htdocs/fourn/class/fournisseur.facture-rec.class.php +++ b/htdocs/fourn/class/fournisseur.facture-rec.class.php @@ -705,7 +705,7 @@ public function fetch_lines() */ $sql = 'SELECT l.rowid,'; - $sql .= ' l.fk_facture_fourn, l.fk_parent_line, l.fk_product, l.ref, l.label, l.description,'; + $sql .= ' l.fk_facture_fourn, l.fk_parent_line, l.fk_product, l.ref as ref_supplier, l.label, l.description,'; $sql .= ' l.pu_ht, l.pu_ttc, l.qty, l.remise_percent, l.fk_remise_except, l.vat_src_code, l.tva_tx,'; $sql .= ' l.localtax1_tx, l.localtax2_tx, l.localtax1_type, l.localtax2_type,'; $sql .= ' l.total_ht, l.total_tva, l.total_ttc, total_localtax1, total_localtax2,'; @@ -734,10 +734,15 @@ public function fetch_lines() $line->fk_facture_fourn = $objp->fk_facture_fourn; $line->fk_parent = $objp->fk_parent_line; $line->fk_product = $objp->fk_product; - $line->ref_supplier = $objp->ref; + $line->ref = $objp->product_ref; // Ref of product + $line->product_ref = $objp->product_ref; // Ref of product + $line->product_label = $objp->product_label; + $line->product_desc = $objp->product_desc; + $line->ref_supplier = $objp->ref_supplier; $line->label = $objp->label; $line->description = $objp->description; $line->pu_ht = $objp->pu_ht; + $line->subprice = $objp->pu_ht; $line->pu_ttc = $objp->pu_ttc; $line->qty = $objp->qty; $line->remise_percent = $objp->remise_percent; diff --git a/htdocs/fourn/facture/card-rec.php b/htdocs/fourn/facture/card-rec.php index 7d6e4c1b7727d..f2e203aae2fb9 100644 --- a/htdocs/fourn/facture/card-rec.php +++ b/htdocs/fourn/facture/card-rec.php @@ -1557,14 +1557,6 @@ $object->fetch_lines(); // Show object lines if (!empty($object->lines)) { - $canchangeproduct = 1; - // To set ref for getNomURL function - foreach ($object->lines as $line) { - $line->ref = $line->label; - $line->product_label = $line->label; - $line->subprice = $line->pu_ht; - } - global $canchangeproduct; $canchangeproduct = 0; From 17b14362848c3b217b903739a878cc91e44aef3b Mon Sep 17 00:00:00 2001 From: bybel Date: Fri, 20 Mar 2026 17:18:16 +0100 Subject: [PATCH 137/172] update mistaken field in recruitment api (#37550) * update mistaken field in recruitment api * Update api_recruitments.class.php --------- Co-authored-by: Laurent Destailleur --- htdocs/recruitment/class/api_recruitments.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/recruitment/class/api_recruitments.class.php b/htdocs/recruitment/class/api_recruitments.class.php index 1aaff616496e2..b36c3d67b7405 100644 --- a/htdocs/recruitment/class/api_recruitments.class.php +++ b/htdocs/recruitment/class/api_recruitments.class.php @@ -418,11 +418,11 @@ public function postCandidature($request_data = null) foreach ($request_data as $field => $value) { if ($field === 'caller') { // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller - $this->jobposition->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09'); + $this->candidature->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09'); continue; } - $this->jobposition->$field = $this->_checkValForAPI($field, $value, $this->jobposition); + $this->candidature->$field = $this->_checkValForAPI($field, $value, $this->candidature); } // Clean data From 29a5befad88db0bee794af282d71edf2a0a4966c Mon Sep 17 00:00:00 2001 From: William Desportes Date: Fri, 20 Mar 2026 18:36:32 +0100 Subject: [PATCH 138/172] Fix getURLContent should serialize arguments and allow array for CURLOPT_POSTFIELDS (#37560) ``` PHP Warning: Array to string conversion in htdocs/core/lib/geturl.lib.php on line 52 ``` Backport of ec2f4146c35e2e24b6e4c5cf7aaddc2e67b43ecc --- htdocs/core/lib/geturl.lib.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/htdocs/core/lib/geturl.lib.php b/htdocs/core/lib/geturl.lib.php index 3398189a7717e..9d91365a73805 100644 --- a/htdocs/core/lib/geturl.lib.php +++ b/htdocs/core/lib/geturl.lib.php @@ -28,13 +28,13 @@ * - you can set MAIN_SECURITY_ANTI_SSRF_SERVER_IP to set static ip of server * - common local lookup ips like 127.*.*.* are automatically added * - * @param string $url URL to call. - * @param string $postorget 'POST', 'GET', 'HEAD', 'PUT', 'PUTALREADYFORMATED', 'POSTALREADYFORMATED', 'DELETE' - * @param string $param Parameters of URL (x=value1&y=value2) or may be a formated content with $postorget='PUTALREADYFORMATED' - * @param integer $followlocation 0=Do not follow, 1=Follow location. - * @param string[] $addheaders Array of string to add into header. Example: ('Accept: application/xrds+xml', ....) - * @param string[] $allowedschemes List of schemes that are allowed ('http' + 'https' only by default) - * @param int $localurl 0=Only external URL are possible, 1=Only local URL, 2=Both external and local URL are allowed. + * @param string $url URL to call. + * @param string $postorget 'POST', 'GET', 'HEAD', 'PUT', 'PUTALREADYFORMATED', 'POSTALREADYFORMATED', 'DELETE' + * @param string|array $param Parameters of URL (x=value1&y=value2) or may be a formated content with $postorget='PUTALREADYFORMATED' + * @param integer $followlocation 0=Do not follow, 1=Follow location. + * @param string[] $addheaders Array of string to add into header. Example: ('Accept: application/xrds+xml', ....) + * @param string[] $allowedschemes List of schemes that are allowed ('http' + 'https' only by default) + * @param int $localurl 0=Only external URL are possible, 1=Only local URL, 2=Both external and local URL are allowed. * @return array Returns an associative array containing the response from the server array('content'=>response, 'curl_error_no'=>errno, 'curl_error_msg'=>errmsg...) */ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = 1, $addheaders = array(), $allowedschemes = array('http', 'https'), $localurl = 0) @@ -47,7 +47,7 @@ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = $PROXY_USER = empty($conf->global->MAIN_PROXY_USER) ? 0 : $conf->global->MAIN_PROXY_USER; $PROXY_PASS = empty($conf->global->MAIN_PROXY_PASS) ? 0 : $conf->global->MAIN_PROXY_PASS; - dol_syslog("getURLContent postorget=".$postorget." URL=".$url." param=".$param); + dol_syslog("getURLContent postorget=".$postorget." URL=".$url." json_encode(param)=".json_encode($param)); //setting the curl parameters. $ch = curl_init(); From 6cbb7ef6db23cd58d56d2429b09ad2f27e0b2b49 Mon Sep 17 00:00:00 2001 From: Alexandre SPANGARO Date: Wed, 25 Mar 2026 00:56:23 +0100 Subject: [PATCH 139/172] FIX #37461 #37511 Accountancy - Bank journal - Problem of cache (#37603) --- htdocs/accountancy/journal/bankjournal.php | 56 +++++++++++++++++++--- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/htdocs/accountancy/journal/bankjournal.php b/htdocs/accountancy/journal/bankjournal.php index aa17090174e18..d49f035bac3de 100644 --- a/htdocs/accountancy/journal/bankjournal.php +++ b/htdocs/accountancy/journal/bankjournal.php @@ -788,43 +788,85 @@ } elseif (in_array($tabtype[$key], array('sc', 'payment_sc'))) { // If payment is payment of social contribution $bookkeeping->subledger_account = ''; $bookkeeping->subledger_label = ''; - $accountingaccount->fetch(null, $k, true); // TODO Use a cache + if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(null, $k, true); + $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + } $bookkeeping->numero_compte = $k; $bookkeeping->label_compte = $accountingaccount->label; } elseif ($tabtype[$key] == 'payment_vat') { $bookkeeping->subledger_account = ''; $bookkeeping->subledger_label = ''; - $accountingaccount->fetch(null, $k, true); // TODO Use a cache + if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(null, $k, true); + $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + } $bookkeeping->numero_compte = $k; $bookkeeping->label_compte = $accountingaccount->label; } elseif ($tabtype[$key] == 'payment_donation') { $bookkeeping->subledger_account = ''; $bookkeeping->subledger_label = ''; - $accountingaccount->fetch(null, $k, true); // TODO Use a cache + if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(null, $k, true); + $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + } $bookkeeping->numero_compte = $k; $bookkeeping->label_compte = $accountingaccount->label; } elseif ($tabtype[$key] == 'member') { $bookkeeping->subledger_account = ''; $bookkeeping->subledger_label = ''; - $accountingaccount->fetch(null, $k, true); // TODO Use a cache + if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(null, $k, true); + $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + } $bookkeeping->numero_compte = $k; $bookkeeping->label_compte = $accountingaccount->label; } elseif ($tabtype[$key] == 'payment_loan') { $bookkeeping->subledger_account = ''; $bookkeeping->subledger_label = ''; - $accountingaccount->fetch(null, $k, true); // TODO Use a cache + if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(null, $k, true); + $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + } $bookkeeping->numero_compte = $k; $bookkeeping->label_compte = $accountingaccount->label; } elseif ($tabtype[$key] == 'payment_various') { $bookkeeping->subledger_account = $k; $bookkeeping->subledger_label = $tabcompany[$key]['name']; - $accountingaccount->fetch(null, $tabpay[$key]["account_various"], true); // TODO Use a cache + if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(null, $k, true); + $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + } $bookkeeping->numero_compte = $tabpay[$key]["account_various"]; $bookkeeping->label_compte = $accountingaccount->label; } elseif ($tabtype[$key] == 'banktransfert') { $bookkeeping->subledger_account = ''; $bookkeeping->subledger_label = ''; - $accountingaccount->fetch(null, $k, true); // TODO Use a cache + if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + $accountingaccount = new AccountingAccount($db); + $accountingaccount->fetch(null, $k, true); + $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + } else { + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + } $bookkeeping->numero_compte = $k; $bookkeeping->label_compte = $accountingaccount->label; } else { From fd93c50c1966c17d20b4ad32e785509caf3aa3b5 Mon Sep 17 00:00:00 2001 From: atm-lucas <121817516+atm-lucasmantegari@users.noreply.github.com> Date: Wed, 25 Mar 2026 01:14:37 +0100 Subject: [PATCH 140/172] fix: add null check before setting _renderItem on jQuery UI autocomplete widget (#37597) * fix: add null check before setting _renderItem on jQuery UI autocomplete widget * const --- htdocs/core/js/lib_head.js.php | 15 +++++++++------ htdocs/core/lib/ajax.lib.php | 16 ++++++++++------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/htdocs/core/js/lib_head.js.php b/htdocs/core/js/lib_head.js.php index 7c2bff01268f3..d6de78638bb98 100644 --- a/htdocs/core/js/lib_head.js.php +++ b/htdocs/core/js/lib_head.js.php @@ -924,12 +924,15 @@ function confirmConstantAction(action, url, code, input, box, entity, yesButton, }) .addClass( "ui-widget ui-widget-content ui-corner-left dolibarrcombobox" ); - input.data("ui-autocomplete")._renderItem = function( ul, item ) { - return $("
  • ") - .data( "ui-autocomplete-item", item ) // jQuery UI > 1.10.0 - .append( "" + item.label + "" ) - .appendTo( ul ); - }; + const widgetInstance = input.data("ui-autocomplete"); + if (widgetInstance) { + widgetInstance._renderItem = function( ul, item ) { + return $("
  • ") + .data( "ui-autocomplete-item", item ) // jQuery UI > 1.10.0 + .append( "" + item.label + "" ) + .appendTo( ul ); + }; + } this.button = $( "" ) .attr( "tabIndex", -1 ) diff --git a/htdocs/core/lib/ajax.lib.php b/htdocs/core/lib/ajax.lib.php index 933a642251224..7d333c0f2bf9f 100644 --- a/htdocs/core/lib/ajax.lib.php +++ b/htdocs/core/lib/ajax.lib.php @@ -278,12 +278,16 @@ function ajax_autocompleter($selected, $htmlname, $url, $urloption = '', $minLen $("#search_'.$htmlnamejquery.'").trigger("change"); // We have changed value of the combo select, we must be sure to trigger all js hook binded on this event. This is required to trigger other javascript change method binded on original field by other code. } ,delay: 500 - }).data("'.$dataforrenderITem.'")._renderItem = function( ul, item ) { - return $("
  • ") - .data( "'.$dataforitem.'", item ) // jQuery UI > 1.10.0 - .append( \'\' + item.label + "" ) - .appendTo(ul); - }; + }); + const widgetData = $("input#search_'.$htmlnamejquery.'").data("'.$dataforrenderITem.'"); + if (widgetData) { + widgetData._renderItem = function( ul, item ) { + return $("
  • ") + .data( "'.$dataforitem.'", item ) // jQuery UI > 1.10.0 + .append( \'\' + item.label + "" ) + .appendTo(ul); + }; + } });'; $script .= ''; From 0dd27f2ac6499e6b5f7e8fcf6c809cea6b3740db Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 25 Mar 2026 13:37:28 +0100 Subject: [PATCH 141/172] Fix sanitize param groupby in customreport --- htdocs/core/customreports.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/htdocs/core/customreports.php b/htdocs/core/customreports.php index ca6e61e3b1cdf..ade6acb30fb90 100644 --- a/htdocs/core/customreports.php +++ b/htdocs/core/customreports.php @@ -224,19 +224,20 @@ // $arrayofvaluesforgroupby will be used to forge lael of each grouped series if (is_array($search_groupby) && count($search_groupby)) { foreach ($search_groupby as $gkey => $gval) { - $gvalwithoutprefix = preg_replace('/^[a-z]+\./', '', $gval); + $gvalwithoutprefix = preg_replace('/^[a-z]+\./i', '', $gval); + $gvalsanitized = preg_replace('/[^a-z0-9\._\-]+/i', '', $gval); - if (preg_match('/\-year$/', $search_groupby[$gkey])) { - $tmpval = preg_replace('/\-year$/', '', $search_groupby[$gkey]); + if (preg_match('/\-year$/', $gvalsanitized)) { + $tmpval = preg_replace('/\-year$/', '', $gvalsanitized); $fieldtocount .= 'DATE_FORMAT('.$tmpval.", '%Y')"; - } elseif (preg_match('/\-month$/', $search_groupby[$gkey])) { - $tmpval = preg_replace('/\-month$/', '', $search_groupby[$gkey]); + } elseif (preg_match('/\-month$/', $gvalsanitized)) { + $tmpval = preg_replace('/\-month$/', '', $gvalsanitized); $fieldtocount .= 'DATE_FORMAT('.$tmpval.", '%Y-%m')"; - } elseif (preg_match('/\-day$/', $search_groupby[$gkey])) { - $tmpval = preg_replace('/\-day$/', '', $search_groupby[$gkey]); + } elseif (preg_match('/\-day$/', $gvalsanitized)) { + $tmpval = preg_replace('/\-day$/', '', $gvalsanitized); $fieldtocount .= 'DATE_FORMAT('.$tmpval.", '%Y-%m-%d')"; } else { - $fieldtocount = $search_groupby[$gkey]; + $fieldtocount = $gvalsanitized; } $sql = 'SELECT DISTINCT '.$fieldtocount.' as val'; From 553f29e45b5791fde17cc6fb5eca2fafe1c06c6f Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 25 Mar 2026 13:58:27 +0100 Subject: [PATCH 142/172] Fix what seems to be a regression when doing #37511 and #37461 --- htdocs/accountancy/journal/bankjournal.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/htdocs/accountancy/journal/bankjournal.php b/htdocs/accountancy/journal/bankjournal.php index d49f035bac3de..194958d561f02 100644 --- a/htdocs/accountancy/journal/bankjournal.php +++ b/htdocs/accountancy/journal/bankjournal.php @@ -848,12 +848,12 @@ } elseif ($tabtype[$key] == 'payment_various') { $bookkeeping->subledger_account = $k; $bookkeeping->subledger_label = $tabcompany[$key]['name']; - if (empty($conf->cache['accountingaccountincurrententity'][$k])) { + if (empty($conf->cache['accountingaccountincurrententity'][$tabpay[$key]["account_various"]])) { $accountingaccount = new AccountingAccount($db); - $accountingaccount->fetch(null, $k, true); - $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; + $accountingaccount->fetch(null, $tabpay[$key]["account_various"], true); + $conf->cache['accountingaccountincurrententity'][$tabpay[$key]["account_various"]] = $accountingaccount; } else { - $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; + $accountingaccount = $conf->cache['accountingaccountincurrententity'][$tabpay[$key]["account_various"]]; } $bookkeeping->numero_compte = $tabpay[$key]["account_various"]; $bookkeeping->label_compte = $accountingaccount->label; From 35b9ea957875c8bcc880fd570e08a8d36325b7d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20FRANCE?= Date: Thu, 26 Mar 2026 01:44:20 +0100 Subject: [PATCH 143/172] fix parameter (V20 fix) (#37616) --- htdocs/accountancy/journal/bankjournal.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/htdocs/accountancy/journal/bankjournal.php b/htdocs/accountancy/journal/bankjournal.php index 194958d561f02..2037b17bf6284 100644 --- a/htdocs/accountancy/journal/bankjournal.php +++ b/htdocs/accountancy/journal/bankjournal.php @@ -790,7 +790,7 @@ $bookkeeping->subledger_label = ''; if (empty($conf->cache['accountingaccountincurrententity'][$k])) { $accountingaccount = new AccountingAccount($db); - $accountingaccount->fetch(null, $k, true); + $accountingaccount->fetch(0, $k, true); $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; } else { $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; @@ -802,7 +802,7 @@ $bookkeeping->subledger_label = ''; if (empty($conf->cache['accountingaccountincurrententity'][$k])) { $accountingaccount = new AccountingAccount($db); - $accountingaccount->fetch(null, $k, true); + $accountingaccount->fetch(0, $k, true); $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; } else { $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; @@ -814,7 +814,7 @@ $bookkeeping->subledger_label = ''; if (empty($conf->cache['accountingaccountincurrententity'][$k])) { $accountingaccount = new AccountingAccount($db); - $accountingaccount->fetch(null, $k, true); + $accountingaccount->fetch(0, $k, true); $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; } else { $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; @@ -826,7 +826,7 @@ $bookkeeping->subledger_label = ''; if (empty($conf->cache['accountingaccountincurrententity'][$k])) { $accountingaccount = new AccountingAccount($db); - $accountingaccount->fetch(null, $k, true); + $accountingaccount->fetch(0, $k, true); $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; } else { $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; @@ -838,7 +838,7 @@ $bookkeeping->subledger_label = ''; if (empty($conf->cache['accountingaccountincurrententity'][$k])) { $accountingaccount = new AccountingAccount($db); - $accountingaccount->fetch(null, $k, true); + $accountingaccount->fetch(0, $k, true); $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; } else { $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; @@ -850,7 +850,7 @@ $bookkeeping->subledger_label = $tabcompany[$key]['name']; if (empty($conf->cache['accountingaccountincurrententity'][$tabpay[$key]["account_various"]])) { $accountingaccount = new AccountingAccount($db); - $accountingaccount->fetch(null, $tabpay[$key]["account_various"], true); + $accountingaccount->fetch(0, $tabpay[$key]["account_various"], true); $conf->cache['accountingaccountincurrententity'][$tabpay[$key]["account_various"]] = $accountingaccount; } else { $accountingaccount = $conf->cache['accountingaccountincurrententity'][$tabpay[$key]["account_various"]]; @@ -862,7 +862,7 @@ $bookkeeping->subledger_label = ''; if (empty($conf->cache['accountingaccountincurrententity'][$k])) { $accountingaccount = new AccountingAccount($db); - $accountingaccount->fetch(null, $k, true); + $accountingaccount->fetch(0, $k, true); $conf->cache['accountingaccountincurrententity'][$k] = $accountingaccount; } else { $accountingaccount = $conf->cache['accountingaccountincurrententity'][$k]; From ea1611a425f20948daaf42583fc279217979ca63 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Thu, 26 Mar 2026 08:28:47 +0100 Subject: [PATCH 144/172] FIX: do not print Extrafields in PDF if printable is 0 --- htdocs/core/class/commondocgenerator.class.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/core/class/commondocgenerator.class.php b/htdocs/core/class/commondocgenerator.class.php index a333a0b5fc2e1..0c51aae69455c 100644 --- a/htdocs/core/class/commondocgenerator.class.php +++ b/htdocs/core/class/commondocgenerator.class.php @@ -1458,8 +1458,8 @@ public function getExtrafieldsInHtml($object, $outputlangs, $params = array()) } $disableOnEmpty = 0; - if ($enabled && !empty($extrafields->attributes[$object->table_element]['printable'][$key])) { - $printable = intval($extrafields->attributes[$object->table_element]['printable'][$key]); + $printable = intval($extrafields->attributes[$object->table_element]['printable'][$key]); + if ($enabled && !empty($printable)) { if (in_array($printable, $params['printableEnable']) || in_array($printable, $params['printableEnableNotEmpty'])) { $enabled = 1; } @@ -1469,7 +1469,7 @@ public function getExtrafieldsInHtml($object, $outputlangs, $params = array()) } } - if (empty($enabled)) { + if (empty($enabled) || empty($printable)) { continue; } From bea0d2c73c7dcbeb2c2fd2922b03c38b9d28640a Mon Sep 17 00:00:00 2001 From: lvessiller-opendsi Date: Thu, 26 Mar 2026 10:03:17 +0100 Subject: [PATCH 145/172] Prepare 18.0.9 (#37625) * Prepare 18.0.9 --- ChangeLog | 87 +++++++++++++++++++++++++++++++++++++++++ htdocs/filefunc.inc.php | 2 +- 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index c8c324bfd5fee..58eafa4a37cf9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,93 @@ English Dolibarr ChangeLog -------------------------------------------------------------- +***** ChangeLog for 18.0.9 compared to 18.0.8 ***** +100 files changed, 2283 insertions(+), 591 deletions(-) + +FIX: product ref was not printed on supplier recurring invoice (#37535) +FIX: show export full documents checkbox on change format in accountancy export (#37468) +FIX: remove unused var +FIX: API Warehouse : Error 401 when getting warehouse by id (backport from 22) +FIX: - Fix doc preview in comm card +FIX: Entity on group ticket insertion (#37370) +FIX: display of DLC/DLUO in tooltip (#37164) +FIX: Backport fix on v20 for result page of compta (/compta/resultat/index.php) (#37127) +FIX: Backport fix on v20 for result page of compta (/compta/resultat/index.php). The expense report were not included when the module was activated +FIX(API, thirdparties): get fixed amount discounts (#37068) +FIX: #GHSA-w5j3-8fcr-h87w (#36868) +FIX(ticket): check on TICKET_IMAGE_PUBLIC_INTERFACE (#36833) +FIX: warning accountancy export from external module (#36832) +FIX: php8.1 warning in syslog message +FIX: TakePos sometimes thirdpartyid = undefined +FIX: remove stock correctly when reception is deleted (like 82e092f) +FIX: finished regex in product import (#36770) +FIX: re-create API temp dir after purging temp files +FIX: undefined variables on create invoice card from order (backport from v19) +FIX: Missing Product ref in Bom stats +FIX: fix #36401 (for v17.0) doesn't work in v18.0+ because of variable renaming +FIX: propal shipping and availability update (v18+) +FIX: PR#36401 fixed a missing GETPOSTISSET() but the check involves a variable ($taskid) that was renamed ($tmptaskid) in 18.0 +SEC: FIX: #36430 permissions not checked on other tabs of HRM evaluation card +FIX: Remove Date: Thu, 26 Mar 2026 10:57:44 +0100 Subject: [PATCH 146/172] FIX: do not print Extrafields in PDF if printable is 0 --- htdocs/core/lib/geturl.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/lib/geturl.lib.php b/htdocs/core/lib/geturl.lib.php index 001ffaf555d73..b32d9e840bf36 100644 --- a/htdocs/core/lib/geturl.lib.php +++ b/htdocs/core/lib/geturl.lib.php @@ -30,7 +30,7 @@ * * @param string $url URL to call. * @param string $postorget 'POST', 'GET', 'HEAD', 'PUT', 'PUTALREADYFORMATED', 'POSTALREADYFORMATED', 'DELETE' - * @param string|array $param Parameters of URL (x=value1&y=value2) or may be a formated content with $postorget='PUTALREADYFORMATED' + * @param string|array $param Parameters of URL (x=value1&y=value2) or may be a formatted content with $postorget='PUTALREADYFORMATED' * @param integer $followlocation 0=Do not follow, 1=Follow location. * @param string[] $addheaders Array of string to add into header. Example: ('Accept: application/xrds+xml', ....) * @param string[] $allowedschemes List of schemes that are allowed ('http' + 'https' only by default) From cdb7d97a7be394f7b3175e662d0ff14af513c295 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Fri, 27 Mar 2026 08:35:59 +0100 Subject: [PATCH 147/172] fix CI --- ChangeLog | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 58eafa4a37cf9..6db7b2e81f040 100644 --- a/ChangeLog +++ b/ChangeLog @@ -39,7 +39,7 @@ FIX: 18.0: GETPOSTDATE and buildParamDate assumed HTTP param names 'minute' and FIX: not remove value of others extra-fields on update extras action NEW: Automatically release docker image for each GitHub release FIX: getDolGlobalInt -SEC: fix IDOR attack on employee evaluation. Missing permision test https://github.com/atm-florianm/dolibarr/commit/7ed0af2a138a34e7c7005b95c85ffc791976a6cf +SEC: fix IDOR attack on employee evaluation. Missing permission test https://github.com/atm-florianm/dolibarr/commit/7ed0af2a138a34e7c7005b95c85ffc791976a6cf FIX: Implementation of multi-company compatibility with inventory/warehouse management FIX: 17.0: perweek.php resets task progress to 0% when: (#36401) FIX: DA027383: permissions not checked on HRM evaluation card (#36328) (#36399) @@ -61,7 +61,7 @@ FIX: wrong check of hook return FIX: 16.0: extrafield of type link to category causes SQL error in selectForFormsList() (#36074) FIX: 16.0 (up to 19.0): extrafield of type link to category causes SQL error in selectForFormsList FIX: missing "printFieldListValue" hook (#35990) -FIX: add action paramter +FIX: add action parameter FIX: remove duplicate object FIX: expense report card: use correct bank module designator for detection FIX: wrong check of hook return From f50508b84a0a61262916531bb01d232ae35dae0e Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 27 Mar 2026 10:23:07 +0100 Subject: [PATCH 148/172] FIX a param must not come from end user entry. --- htdocs/core/ajax/saveinplace.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/htdocs/core/ajax/saveinplace.php b/htdocs/core/ajax/saveinplace.php index 2f88cebf58b58..8f964b55148c3 100644 --- a/htdocs/core/ajax/saveinplace.php +++ b/htdocs/core/ajax/saveinplace.php @@ -120,6 +120,7 @@ $feature = 'fournisseur'; $feature2 = 'facture'; } + //var_dump(GETPOST('action','aZ09')); //var_dump($newelement.'-'.$subelement."-".$feature."-".$object_id); $check_access = restrictedArea($user, $feature, $object_id, '', $feature2); @@ -205,7 +206,7 @@ $object->fk_element = $fk_element; $object->element = $element; - $ret = $object->$savemethodname($field, $newvalue, $table_element, $fk_element, $format); + $ret = $object->setValueFrom($field, $newvalue, $object->table_element, $fk_element, $format); if ($ret > 0) { if ($type == 'numeric') { $value = price($newvalue); From 69cee514b55bfe1d2652548a46627602061e4fb0 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 27 Mar 2026 10:30:26 +0100 Subject: [PATCH 149/172] Doc --- htdocs/core/ajax/saveinplace.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/htdocs/core/ajax/saveinplace.php b/htdocs/core/ajax/saveinplace.php index 8f964b55148c3..0f72d4306758c 100644 --- a/htdocs/core/ajax/saveinplace.php +++ b/htdocs/core/ajax/saveinplace.php @@ -71,7 +71,6 @@ $value = ($type == 'ckeditor' ? GETPOST('value', '', 2) : GETPOST('value', 'alpha', 2)); $loadmethod = GETPOST('loadmethod', 'alpha', 2); $savemethod = GETPOST('savemethod', 'alpha', 2); - $savemethodname = (!empty($savemethod) ? $savemethod : 'setValueFrom'); $newelement = $element; $view = ''; @@ -120,7 +119,6 @@ $feature = 'fournisseur'; $feature2 = 'facture'; } - //var_dump(GETPOST('action','aZ09')); //var_dump($newelement.'-'.$subelement."-".$feature."-".$object_id); $check_access = restrictedArea($user, $feature, $object_id, '', $feature2); @@ -194,11 +192,7 @@ } } - if (!$error) { - if ((isset($object) && !is_object($object)) || empty($savemethod)) { - $object = new GenericObject($db); - } - + if (!$error && isset($object) && is_object($object)) { // Specific for add_object_linked() // TODO add a function for variable treatment $object->ext_fk_element = $newvalue; From 48ec90f5c4e33b2b2d7ac1d64923b5a26f7a38f6 Mon Sep 17 00:00:00 2001 From: atm-corentin <165782689+atm-corentin@users.noreply.github.com> Date: Fri, 3 Apr 2026 03:32:58 +0200 Subject: [PATCH 150/172] fix(ticket): clear stale attachments when opening a new ticket form (#37684) --- htdocs/core/modules/modTicket.class.php | 2 +- htdocs/ticket/card.php | 4 ++++ htdocs/ticket/list.php | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/htdocs/core/modules/modTicket.class.php b/htdocs/core/modules/modTicket.class.php index 18f0d368cfda8..db4fb8bf83c78 100644 --- a/htdocs/core/modules/modTicket.class.php +++ b/htdocs/core/modules/modTicket.class.php @@ -256,7 +256,7 @@ public function __construct($db) 'type' => 'left', 'titre' => 'NewTicket', 'mainmenu' => 'ticket', - 'url' => '/ticket/card.php?action=create', + 'url' => '/ticket/card.php?action=create&mode=init', 'langs' => 'ticket', 'position' => 102, 'enabled' => '$conf->ticket->enabled', diff --git a/htdocs/ticket/card.php b/htdocs/ticket/card.php index 16134ca2d7a9b..85e3001a30e62 100644 --- a/htdocs/ticket/card.php +++ b/htdocs/ticket/card.php @@ -713,6 +713,10 @@ print load_fiche_titre($langs->trans('NewTicket'), '', 'ticket'); + if (GETPOST("mode", "aZ09") == 'init' && empty($_POST)) { + $formticket->clear_attached_files(); + } + $formticket->trackid = ''; // TODO Use a unique key to avoid conflict in upload file feature $formticket->withfromsocid = $socid ? $socid : $user->socid; $formticket->withfromcontactid = $contactid ? $contactid : ''; diff --git a/htdocs/ticket/list.php b/htdocs/ticket/list.php index 25bd0e6185466..24a9ff7371ed7 100644 --- a/htdocs/ticket/list.php +++ b/htdocs/ticket/list.php @@ -730,7 +730,7 @@ print ''; } -$url = DOL_URL_ROOT.'/ticket/card.php?action=create'.($socid ? '&socid='.$socid : '').($projectid ? '&origin=projet_project&originid='.$projectid : ''); +$url = DOL_URL_ROOT.'/ticket/card.php?action=create&mode=init'.($socid ? '&socid='.$socid : '').($projectid ? '&origin=projet_project&originid='.$projectid : ''); if (!empty($socid)) { $url .= '&socid='.$socid; } From 60417a3b4e342c6030ccf3818062ab5f8f567008 Mon Sep 17 00:00:00 2001 From: ThomasNgr-OpenDSI Date: Tue, 7 Apr 2026 13:41:22 +0200 Subject: [PATCH 151/172] FIX extrafield selectlist when there is a linked table (#37706) --- htdocs/core/class/commonobject.class.php | 18 ++++++++++---- htdocs/core/class/extrafields.class.php | 24 ++++++++++++------- .../class/html.formwebportal.class.php | 6 ++++- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index 16717b064ef36..83e40cece8575 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -7681,7 +7681,11 @@ function handlemultiinputdisabling(htmlname){ } if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { list($parentName, $parentField) = explode('|', $InfoFieldList[3]); - $keyList .= ', '.$parentField; + if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) { + $keyList .= ', main.'.$parentField; + } else { + $keyList .= ', '.$parentField; + } } $filter_categorie = false; @@ -7906,10 +7910,6 @@ function handlemultiinputdisabling(htmlname){ $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid'); - if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { - list($parentName, $parentField) = explode('|', $InfoFieldList[3]); - $keyList .= ', '.$parentField; - } if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) { if (strpos($InfoFieldList[4], 'extra.') !== false) { $keyList = 'main.'.$InfoFieldList[2].' as rowid'; @@ -7917,6 +7917,14 @@ function handlemultiinputdisabling(htmlname){ $keyList = $InfoFieldList[2].' as rowid'; } } + if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { + list($parentName, $parentField) = explode('|', $InfoFieldList[3]); + if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) { + $keyList .= ', main.'.$parentField; + } else { + $keyList .= ', '.$parentField; + } + } $filter_categorie = false; if (count($InfoFieldList) > 5) { diff --git a/htdocs/core/class/extrafields.class.php b/htdocs/core/class/extrafields.class.php index 54e02a07454d1..5eff99c86c811 100644 --- a/htdocs/core/class/extrafields.class.php +++ b/htdocs/core/class/extrafields.class.php @@ -1390,10 +1390,6 @@ public function showInputField($key, $value, $moreparam = '', $keysuffix = '', $ $parentField = ''; $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid'); - if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { - list($parentName, $parentField) = explode('|', $InfoFieldList[3]); - $keyList .= ', '.$parentField; - } if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) { if (strpos($InfoFieldList[4], 'extra.') !== false) { $keyList = 'main.'.$InfoFieldList[2].' as rowid'; @@ -1401,6 +1397,14 @@ public function showInputField($key, $value, $moreparam = '', $keysuffix = '', $ $keyList = $InfoFieldList[2].' as rowid'; } } + if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { + list($parentName, $parentField) = explode('|', $InfoFieldList[3]); + if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) { + $keyList .= ', main.'.$parentField; + } else { + $keyList .= ', '.$parentField; + } + } $filter_categorie = false; if (count($InfoFieldList) > 5) { @@ -1615,10 +1619,6 @@ public function showInputField($key, $value, $moreparam = '', $keysuffix = '', $ $parentField = ''; $keyList = (empty($InfoFieldList[2]) ? 'rowid' : $InfoFieldList[2].' as rowid'); - if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { - list($parentName, $parentField) = explode('|', $InfoFieldList[3]); - $keyList .= ', '.$parentField; - } if (count($InfoFieldList) > 4 && !empty($InfoFieldList[4])) { if (strpos($InfoFieldList[4], 'extra.') !== false) { $keyList = 'main.'.$InfoFieldList[2].' as rowid'; @@ -1626,6 +1626,14 @@ public function showInputField($key, $value, $moreparam = '', $keysuffix = '', $ $keyList = $InfoFieldList[2].' as rowid'; } } + if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { + list($parentName, $parentField) = explode('|', $InfoFieldList[3]); + if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) { + $keyList .= ', main.'.$parentField; + } else { + $keyList .= ', '.$parentField; + } + } $filter_categorie = false; if (count($InfoFieldList) > 5) { diff --git a/htdocs/webportal/class/html.formwebportal.class.php b/htdocs/webportal/class/html.formwebportal.class.php index 6a5197d7bd5ca..2dd2a9a865156 100644 --- a/htdocs/webportal/class/html.formwebportal.class.php +++ b/htdocs/webportal/class/html.formwebportal.class.php @@ -804,7 +804,11 @@ public function showInputField($val, $key, $value, $moreparam = '', $keysuffix = } if (count($InfoFieldList) > 3 && !empty($InfoFieldList[3])) { list($parentName, $parentField) = explode('|', $InfoFieldList[3]); - $keyList .= ', ' . $parentField; + if (!empty($InfoFieldList[4]) && strpos($InfoFieldList[4], 'extra.') !== false) { + $keyList .= ', main.'.$parentField; + } else { + $keyList .= ', '.$parentField; + } } $filter_categorie = false; From 79d1bf55d7f770bcbf4c343e49b4b3c3975d8628 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 7 Apr 2026 14:00:48 +0200 Subject: [PATCH 152/172] Test --- .github/workflows/pr-18-testldr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-18-testldr.yaml b/.github/workflows/pr-18-testldr.yaml index 32abe8e7e8ef9..84a84e170b5d0 100644 --- a/.github/workflows/pr-18-testldr.yaml +++ b/.github/workflows/pr-18-testldr.yaml @@ -72,7 +72,7 @@ jobs: fi done echo "AUTHOR=$AUTHOR" - echo "reviewers=$(IFS=, ; echo "${FINAL_REVIEWERS[*]}")" >> $GITHUB_OUTPUT + echo reviewers=$(IFS=, ; echo "${FINAL_REVIEWERS[*]}") >> $GITHUB_OUTPUT - name: Assign reviewers if: steps.set-reviewers.outputs.reviewers != '' && ${{env.prid}} != '' From 3a104891876ea8dd31134dc31d59499f0a9a3e2d Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Wed, 8 Apr 2026 20:09:12 +0200 Subject: [PATCH 153/172] FIX: do not print Extrafields in PDF if printable is 0 (#37789) * FIX: do not print Extrafields in PDF if printable is 0 * review github CI --- htdocs/core/class/commondocgenerator.class.php | 6 +++--- htdocs/core/lib/geturl.lib.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/htdocs/core/class/commondocgenerator.class.php b/htdocs/core/class/commondocgenerator.class.php index 2b3adff4ecdeb..141fa6733f553 100644 --- a/htdocs/core/class/commondocgenerator.class.php +++ b/htdocs/core/class/commondocgenerator.class.php @@ -1458,8 +1458,8 @@ public function getExtrafieldsInHtml($object, $outputlangs, $params = array()) } $disableOnEmpty = 0; - if ($enabled && !empty($extrafields->attributes[$object->table_element]['printable'][$key])) { - $printable = intval($extrafields->attributes[$object->table_element]['printable'][$key]); + $printable = (int) $extrafields->attributes[$object->table_element]['printable'][$key]; + if ($enabled && !empty($printable)) { if (in_array($printable, $params['printableEnable']) || in_array($printable, $params['printableEnableNotEmpty'])) { $enabled = 1; } @@ -1469,7 +1469,7 @@ public function getExtrafieldsInHtml($object, $outputlangs, $params = array()) } } - if (empty($enabled)) { + if (empty($enabled) || empty($printable)) { continue; } diff --git a/htdocs/core/lib/geturl.lib.php b/htdocs/core/lib/geturl.lib.php index fd888e2c6539d..88e47374b6166 100644 --- a/htdocs/core/lib/geturl.lib.php +++ b/htdocs/core/lib/geturl.lib.php @@ -30,7 +30,7 @@ * * @param string $url URL to call. * @param string $postorget 'POST', 'GET', 'HEAD', 'PUT', 'PUTALREADYFORMATED', 'POSTALREADYFORMATED', 'DELETE' - * @param string|array $param Parameters of URL (x=value1&y=value2) or may be a formated content with $postorget='PUTALREADYFORMATED' + * @param string|array $param Parameters of URL (x=value1&y=value2) or may be a formatted content with $postorget='PUTALREADYFORMATED' * @param integer $followlocation 0=Do not follow, 1=Follow location. * @param string[] $addheaders Array of string to add into header. Example: ('Accept: application/xrds+xml', ....) * @param string[] $allowedschemes List of schemes that are allowed ('http' + 'https' only by default) From 894f2c175f6c0749ffa91eecf12e29cf183fd606 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Thu, 9 Apr 2026 07:44:36 +0200 Subject: [PATCH 154/172] review btter fix for not print extrafields --- htdocs/core/class/commondocgenerator.class.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/htdocs/core/class/commondocgenerator.class.php b/htdocs/core/class/commondocgenerator.class.php index 0c51aae69455c..f9e1fa816f226 100644 --- a/htdocs/core/class/commondocgenerator.class.php +++ b/htdocs/core/class/commondocgenerator.class.php @@ -1458,7 +1458,10 @@ public function getExtrafieldsInHtml($object, $outputlangs, $params = array()) } $disableOnEmpty = 0; - $printable = intval($extrafields->attributes[$object->table_element]['printable'][$key]); + $printable = 0; + if (isset($extrafields->attributes[$object->table_element]['printable'][$key])) { + $printable = (int) $extrafields->attributes[$object->table_element]['printable'][$key]; + } if ($enabled && !empty($printable)) { if (in_array($printable, $params['printableEnable']) || in_array($printable, $params['printableEnableNotEmpty'])) { $enabled = 1; From fa4c7b36797ec1d605f03908877db08614017056 Mon Sep 17 00:00:00 2001 From: Eric Seigne Date: Thu, 9 Apr 2026 09:40:38 +0200 Subject: [PATCH 155/172] apply develop pre-commit rules --- ChangeLog | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 58eafa4a37cf9..6db7b2e81f040 100644 --- a/ChangeLog +++ b/ChangeLog @@ -39,7 +39,7 @@ FIX: 18.0: GETPOSTDATE and buildParamDate assumed HTTP param names 'minute' and FIX: not remove value of others extra-fields on update extras action NEW: Automatically release docker image for each GitHub release FIX: getDolGlobalInt -SEC: fix IDOR attack on employee evaluation. Missing permision test https://github.com/atm-florianm/dolibarr/commit/7ed0af2a138a34e7c7005b95c85ffc791976a6cf +SEC: fix IDOR attack on employee evaluation. Missing permission test https://github.com/atm-florianm/dolibarr/commit/7ed0af2a138a34e7c7005b95c85ffc791976a6cf FIX: Implementation of multi-company compatibility with inventory/warehouse management FIX: 17.0: perweek.php resets task progress to 0% when: (#36401) FIX: DA027383: permissions not checked on HRM evaluation card (#36328) (#36399) @@ -61,7 +61,7 @@ FIX: wrong check of hook return FIX: 16.0: extrafield of type link to category causes SQL error in selectForFormsList() (#36074) FIX: 16.0 (up to 19.0): extrafield of type link to category causes SQL error in selectForFormsList FIX: missing "printFieldListValue" hook (#35990) -FIX: add action paramter +FIX: add action parameter FIX: remove duplicate object FIX: expense report card: use correct bank module designator for detection FIX: wrong check of hook return From 8d1c7952c3e06a6e4b5ae011043d9e8bfc47c01a Mon Sep 17 00:00:00 2001 From: Eric Seigne Date: Thu, 9 Apr 2026 10:02:18 +0200 Subject: [PATCH 156/172] codespell to fix words --- htdocs/core/lib/geturl.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/lib/geturl.lib.php b/htdocs/core/lib/geturl.lib.php index 001ffaf555d73..b32d9e840bf36 100644 --- a/htdocs/core/lib/geturl.lib.php +++ b/htdocs/core/lib/geturl.lib.php @@ -30,7 +30,7 @@ * * @param string $url URL to call. * @param string $postorget 'POST', 'GET', 'HEAD', 'PUT', 'PUTALREADYFORMATED', 'POSTALREADYFORMATED', 'DELETE' - * @param string|array $param Parameters of URL (x=value1&y=value2) or may be a formated content with $postorget='PUTALREADYFORMATED' + * @param string|array $param Parameters of URL (x=value1&y=value2) or may be a formatted content with $postorget='PUTALREADYFORMATED' * @param integer $followlocation 0=Do not follow, 1=Follow location. * @param string[] $addheaders Array of string to add into header. Example: ('Accept: application/xrds+xml', ....) * @param string[] $allowedschemes List of schemes that are allowed ('http' + 'https' only by default) From ad9328ec86ca6839f6fe4177c8e01e4c102e6160 Mon Sep 17 00:00:00 2001 From: ThomasNgr-OpenDSI Date: Thu, 9 Apr 2026 20:33:34 +0200 Subject: [PATCH 157/172] FIX #GHSA-crgg-h74r-2m8r (#37636) Co-authored-by: Laurent Destailleur --- htdocs/api/class/api_documents.class.php | 51 ++++++++++++++++++++++++ htdocs/document.php | 2 +- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/htdocs/api/class/api_documents.class.php b/htdocs/api/class/api_documents.class.php index c37e8db9f6b1f..2a5967fcc921d 100644 --- a/htdocs/api/class/api_documents.class.php +++ b/htdocs/api/class/api_documents.class.php @@ -104,6 +104,23 @@ public function index($modulepart, $original_file = '') throw new RestException(401); } + if (DolibarrApiAccess::$user->socid > 0) { + if ($sqlprotectagainstexternals) { + $resql = $this->db->query($sqlprotectagainstexternals); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + while ($i < $num) { + $obj = $this->db->fetch_object($resql); + if (DolibarrApiAccess::$user->socid != $obj->fk_soc) { + throw new RestException(403, 'Not allowed to download documents with such a ref'); + } + $i++; + } + } + } + } + $filename = basename($original_file); $original_file_osencoded = dol_osencode($original_file); // New file name encoded in OS encoding charset @@ -181,6 +198,23 @@ public function builddoc($modulepart, $original_file = '', $doctemplate = '', $l throw new RestException(401); } + if (DolibarrApiAccess::$user->socid > 0) { + if ($sqlprotectagainstexternals) { + $resql = $this->db->query($sqlprotectagainstexternals); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + while ($i < $num) { + $obj = $this->db->fetch_object($resql); + if (DolibarrApiAccess::$user->socid != $obj->fk_soc) { + throw new RestException(403, 'Not allowed to download documents with such a ref'); + } + $i++; + } + } + } + } + // --- Generates the document $hidedetails = empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 0 : 1; $hidedesc = empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 0 : 1; @@ -947,6 +981,23 @@ public function delete($modulepart, $original_file) throw new RestException(401); } + if (DolibarrApiAccess::$user->socid > 0) { + if ($sqlprotectagainstexternals) { + $resql = $this->db->query($sqlprotectagainstexternals); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + while ($i < $num) { + $obj = $this->db->fetch_object($resql); + if (DolibarrApiAccess::$user->socid != $obj->fk_soc) { + throw new RestException(403, 'Not allowed to download documents with such a ref'); + } + $i++; + } + } + } + } + $filename = basename($original_file); $original_file_osencoded = dol_osencode($original_file); // New file name encoded in OS encoding charset diff --git a/htdocs/document.php b/htdocs/document.php index 394e470257ad4..6d0203d8c61a5 100644 --- a/htdocs/document.php +++ b/htdocs/document.php @@ -215,7 +215,7 @@ function llxFooter() } // Check security and set return info with full path of file -$check_access = dol_check_secure_access_document($modulepart, $original_file, $entity, $user, ''); +$check_access = dol_check_secure_access_document($modulepart, $original_file, (int) $entity, $user, '', 'read'); $accessallowed = $check_access['accessallowed']; $sqlprotectagainstexternals = $check_access['sqlprotectagainstexternals']; $fullpath_original_file = $check_access['original_file']; // $fullpath_original_file is now a full path name From 06614955351ac50a05f95d45475ea472891d3489 Mon Sep 17 00:00:00 2001 From: ThomasNgr-OpenDSI Date: Thu, 9 Apr 2026 20:33:56 +0200 Subject: [PATCH 158/172] 18 report ghsa 39vm 9q4p 6jjg (#37627) * report ghsa-39vm-9q4p-6jjg (commit 1072e090) * Adapt to 18.0 --------- Co-authored-by: Laurent Destailleur --- htdocs/main.inc.php | 51 +++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/htdocs/main.inc.php b/htdocs/main.inc.php index 264bd10a07cd8..fc4de88872c2f 100644 --- a/htdocs/main.inc.php +++ b/htdocs/main.inc.php @@ -278,6 +278,13 @@ function analyseVarsForSqlAndScriptsInjection(&$var, $type) // Include the conf.php and functions.lib.php and security.lib.php. This defined the constants like DOL_DOCUMENT_ROOT, DOL_DATA_ROOT, DOL_URL_ROOT... require_once 'filefunc.inc.php'; +/** + * @var ?string $php_session_save_handler + * @var ?string $dolibarr_main_force_https + * @var ?string $dolibarr_main_restrict_ip + * @var ?string $dolibarr_nocsrfcheck + * @var ?string $dolibarr_main_demo + */ // If there is a POST parameter to tell to save automatically some POST parameters into cookies, we do it. // This is used for example by form of boxes to save personalization of some options. @@ -619,26 +626,30 @@ function analyseVarsForSqlAndScriptsInjection(&$var, $type) // Note: There is another CSRF protection into the filefunc.inc.php } -// Disable modules (this must be after session_start and after conf has been loaded) -if (GETPOSTISSET('disablemodules')) { - $_SESSION["disablemodules"] = GETPOST('disablemodules', 'alpha'); -} -if (!empty($_SESSION["disablemodules"])) { - $modulepartkeys = array('css', 'js', 'tabs', 'triggers', 'login', 'substitutions', 'menus', 'theme', 'sms', 'tpl', 'barcode', 'models', 'societe', 'hooks', 'dir', 'syslog', 'tpllinkable', 'contactelement', 'moduleforexternal'); - - $disabled_modules = explode(',', $_SESSION["disablemodules"]); - foreach ($disabled_modules as $module) { - if ($module) { - if (empty($conf->$module)) { - $conf->$module = new stdClass(); // To avoid warnings - } - $conf->$module->enabled = false; - foreach ($modulepartkeys as $modulepartkey) { - unset($conf->modules_parts[$modulepartkey][$module]); - } - if ($module == 'fournisseur') { // Special case - $conf->supplier_order->enabled = 0; - $conf->supplier_invoice->enabled = 0; +if (!empty($dolibarr_main_demo)) { + // Disable modules (this must be after session_start and after conf has been loaded) + if (GETPOSTISSET('disablemodules')) { + $_SESSION["disablemodules"] = GETPOST('disablemodules', 'alpha'); + } + if (!empty($_SESSION["disablemodules"])) { + $modulepartkeys = array('css', 'js', 'tabs', 'triggers', 'login', 'substitutions', 'menus', 'theme', 'sms', 'tpl', 'barcode', 'models', 'societe', 'hooks', 'dir', 'syslog', 'tpllinkable', 'contactelement', 'moduleforexternal', 'websitetemplates'); + + $disabled_modules = explode(',', $_SESSION["disablemodules"]); + foreach ($disabled_modules as $module) { + if ($module) { + if (empty($conf->$module)) { + $conf->$module = new stdClass(); // To avoid warnings + } + + $conf->$module->enabled = false; // Old usage + + foreach ($modulepartkeys as $modulepartkey) { + unset($conf->modules_parts[$modulepartkey][$module]); + } + if ($module == 'fournisseur') { // Special case + $conf->supplier_order->enabled = 0; + $conf->supplier_invoice->enabled = 0; + } } } } From ba612dc1a8e60bcc67702f33b97f33c176771b28 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Fri, 10 Apr 2026 12:41:11 +0200 Subject: [PATCH 159/172] 19 fix print pdf extrafields not visiblev2 (#37796) * FIX: do not print Extrafields in PDF if printable is 0 * review github CI * review btter fix for not print extrafields * review btter fix for not print extrafields --- htdocs/core/class/commondocgenerator.class.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/htdocs/core/class/commondocgenerator.class.php b/htdocs/core/class/commondocgenerator.class.php index 141fa6733f553..b5df827338165 100644 --- a/htdocs/core/class/commondocgenerator.class.php +++ b/htdocs/core/class/commondocgenerator.class.php @@ -1458,7 +1458,10 @@ public function getExtrafieldsInHtml($object, $outputlangs, $params = array()) } $disableOnEmpty = 0; - $printable = (int) $extrafields->attributes[$object->table_element]['printable'][$key]; + $printable = 0; + if (isset($extrafields->attributes[$object->table_element]['printable'][$key])) { + $printable = (int) $extrafields->attributes[$object->table_element]['printable'][$key]; + } if ($enabled && !empty($printable)) { if (in_array($printable, $params['printableEnable']) || in_array($printable, $params['printableEnableNotEmpty'])) { $enabled = 1; From 916e318deeb97d99c636cd658e4858e298a60a1d Mon Sep 17 00:00:00 2001 From: atm-jonathan <146709163+atm-jonathan@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:42:20 +0200 Subject: [PATCH 160/172] Fix/doctemplates path entity centralized (#37788) * Fix for multi-entity on doctemplates * clean * construction of the fullpath_original_file --- htdocs/core/lib/files.lib.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/htdocs/core/lib/files.lib.php b/htdocs/core/lib/files.lib.php index 8d82ea46f9fee..c0cb5f7340fc6 100644 --- a/htdocs/core/lib/files.lib.php +++ b/htdocs/core/lib/files.lib.php @@ -2942,9 +2942,15 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, $accessallowed = ($user->admin && basename($original_file) == $original_file && preg_match('/^dolibarr.*\.(log|json)$/', basename($original_file))); $original_file = $dolibarr_main_data_root.'/'.$original_file; } elseif ($modulepart == 'doctemplates' && !empty($dolibarr_main_data_root)) { - // Wrapping for doctemplates $accessallowed = $user->admin; - $original_file = $dolibarr_main_data_root.'/doctemplates/'.$original_file; + $relative_file = $original_file; + $ent = ($entity > 0 ? $entity : $conf->entity); + $path_with_entity = $dolibarr_main_data_root . '/' . $ent . '/doctemplates/' . $relative_file; + if ($ent > 1 && file_exists(dol_osencode($path_with_entity))) { + $original_file = $path_with_entity; + } else { + $original_file = $dolibarr_main_data_root . '/doctemplates/' . $relative_file; + } } elseif ($modulepart == 'doctemplateswebsite' && !empty($dolibarr_main_data_root)) { // Wrapping for doctemplates of websites $accessallowed = ($fuser->hasRight('website', 'write') && preg_match('/\.jpg$/i', basename($original_file))); From 307db313b6394d27d5a6ebcd31bbf69312065355 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 14 Apr 2026 19:31:07 +0200 Subject: [PATCH 161/172] FIX SQL Injection via Operator Injection in Contract Service List This vulnerability was discovered and reported by Nguy Minh Tuan (@minhtuanact) of Sun Cyber Security Research Team. --- htdocs/contrat/services_list.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/htdocs/contrat/services_list.php b/htdocs/contrat/services_list.php index c120a3add4702..6860bf941ba3c 100644 --- a/htdocs/contrat/services_list.php +++ b/htdocs/contrat/services_list.php @@ -323,28 +323,28 @@ } if (!empty($filter_opouvertureprevue) && $filter_opouvertureprevue != -1 && $filter_opouvertureprevue != ' BETWEEN ' && $filter_dateouvertureprevue_start != '') { - $sql .= " AND cd.date_ouverture_prevue ".$filter_opouvertureprevue." '".$db->idate($filter_dateouvertureprevue_start)."'"; + $sql .= " AND cd.date_ouverture_prevue ".preg_replace('/[^<>]/', '', $filter_opouvertureprevue)." '".$db->idate($filter_dateouvertureprevue_start)."'"; } if (!empty($filter_opouvertureprevue) && $filter_opouvertureprevue == ' BETWEEN ') { - $sql .= " AND cd.date_ouverture_prevue ".$filter_opouvertureprevue." '".$db->idate($filter_dateouvertureprevue_start)."' AND '".$db->idate($filter_dateouvertureprevue_end)."'"; + $sql .= " AND cd.date_ouverture_prevue ".$db->sanitize($filter_opouvertureprevue)." '".$db->idate($filter_dateouvertureprevue_start)."' AND '".$db->idate($filter_dateouvertureprevue_end)."'"; } if (!empty($filter_op1) && $filter_op1 != -1 && $filter_op1 != ' BETWEEN ' && $filter_date1_start != '') { - $sql .= " AND cd.date_ouverture ".$filter_op1." '".$db->idate($filter_date1_start)."'"; + $sql .= " AND cd.date_ouverture ".preg_replace('/[^<>]/', '', $filter_op1)." '".$db->idate($filter_date1_start)."'"; } if (!empty($filter_op1) && $filter_op1 == ' BETWEEN ') { - $sql .= " AND cd.date_ouverture ".$filter_op1." '".$db->idate($filter_date1_start)."' AND '".$db->idate($filter_date1_end)."'"; + $sql .= " AND cd.date_ouverture ".$db->sanitize($filter_op1)." '".$db->idate($filter_date1_start)."' AND '".$db->idate($filter_date1_end)."'"; } if (!empty($filter_op2) && $filter_op2 != -1 && $filter_op2 != ' BETWEEN ' && $filter_date2_start != '') { - $sql .= " AND cd.date_fin_validite ".$filter_op2." '".$db->idate($filter_date2_start)."'"; + $sql .= " AND cd.date_fin_validite ".preg_replace('/[^<>]/', '', $filter_op2)." '".$db->idate($filter_date2_start)."'"; } if (!empty($filter_op2) && $filter_op2 == ' BETWEEN ') { - $sql .= " AND cd.date_fin_validite ".$filter_op2." '".$db->idate($filter_date2_start)."' AND '".$db->idate($filter_date2_end)."'"; + $sql .= " AND cd.date_fin_validite ".$db->sanitize($filter_op2)." '".$db->idate($filter_date2_start)."' AND '".$db->idate($filter_date2_end)."'"; } if (!empty($filter_opcloture) && $filter_opcloture != ' BETWEEN ' && $filter_opcloture != -1 && $filter_datecloture_start != '') { - $sql .= " AND cd.date_cloture ".$filter_opcloture." '".$db->idate($filter_datecloture_start)."'"; + $sql .= " AND cd.date_cloture ".preg_replace('/[^<>]/', '', $filter_opcloture)." '".$db->idate($filter_datecloture_start)."'"; } if (!empty($filter_opcloture) && $filter_opcloture == ' BETWEEN ') { - $sql .= " AND cd.date_cloture ".$filter_opcloture." '".$db->idate($filter_datecloture_start)."' AND '".$db->idate($filter_datecloture_end)."'"; + $sql .= " AND cd.date_cloture ".$db->sanitize($filter_opcloture)." '".$db->idate($filter_datecloture_start)."' AND '".$db->idate($filter_datecloture_end)."'"; } // Add where from extra fields include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php'; From 3686e70c1e33714bc22fe385b753cfbd4b449273 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 15 Apr 2026 16:07:59 +0200 Subject: [PATCH 162/172] Doc --- htdocs/core/lib/geturl.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/lib/geturl.lib.php b/htdocs/core/lib/geturl.lib.php index 339abf89c3c47..c8df855a3992d 100644 --- a/htdocs/core/lib/geturl.lib.php +++ b/htdocs/core/lib/geturl.lib.php @@ -30,7 +30,7 @@ * * @param string $url URL to call. * @param string $postorget 'POST', 'GET', 'HEAD', 'PUT', 'PUTALREADYFORMATED', 'POSTALREADYFORMATED', 'DELETE' - * @param string|array $param Parameters of URL (x=value1&y=value2) or may be a formated content with $postorget='PUTALREADYFORMATED' + * @param string|array $param Parameters of URL (x=value1&y=value2) or may be a formatted content with $postorget='PUTALREADYFORMATED' * @param integer $followlocation 0=Do not follow, 1=Follow location. * @param string[] $addheaders Array of string to add into header. Example: ('Accept: application/xrds+xml', ....) * @param string[] $allowedschemes List of schemes that are allowed ('http' + 'https' only by default) From d30da98414328e97dc3df69bfb103369a43d0f54 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 15 Apr 2026 18:40:59 +0200 Subject: [PATCH 163/172] Fix warning --- htdocs/accountancy/journal/bankjournal.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/accountancy/journal/bankjournal.php b/htdocs/accountancy/journal/bankjournal.php index 9a57dbaa93bb3..05cdfdf861a27 100644 --- a/htdocs/accountancy/journal/bankjournal.php +++ b/htdocs/accountancy/journal/bankjournal.php @@ -671,7 +671,7 @@ $accountingaccountpayment->fetch(0, getDolGlobalString('SALARIES_ACCOUNTING_ACCOUNT_PAYMENT'), true); $accountingaccountexpensereport = new AccountingAccount($db); - $accountingaccountexpensereport->fetch(0, $conf->global->ACCOUNTING_ACCOUNT_EXPENSEREPORT, true); + $accountingaccountexpensereport->fetch(0, getDolGlobalString('ACCOUNTING_ACCOUNT_EXPENSEREPORT'), true); $accountingaccountsuspense = new AccountingAccount($db); $accountingaccountsuspense->fetch(0, getDolGlobalString('ACCOUNTING_ACCOUNT_SUSPENSE'), true); From a5307001c6444f9d9d21c83b9cba9583eebbb57f Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 15 Apr 2026 18:52:59 +0200 Subject: [PATCH 164/172] Make a dangerous and very unstable feature hidden. Should never be on as it ha sno doubt that option is not stable and dev not complete. A lot of side effect not yet managed must be managed first before beeing enabled. Related to #37878 --- htdocs/admin/payment.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/htdocs/admin/payment.php b/htdocs/admin/payment.php index f60bed8f0c9bd..fe1111aed8b4c 100644 --- a/htdocs/admin/payment.php +++ b/htdocs/admin/payment.php @@ -270,12 +270,14 @@ print "\n"; // Allow payments on different thirdparties bills but same parent company +/* print ''; print $langs->trans("PaymentOnDifferentThirdBills"); print ''; print $form->selectyesno("FACTURE_PAYMENTS_ON_DIFFERENT_THIRDPARTIES_BILLS", getDolGlobalInt('FACTURE_PAYMENTS_ON_DIFFERENT_THIRDPARTIES_BILLS'), 1); print ''; print "\n"; +*/ // Allow to group payments by mod in rapports print ''; From d6f53cf07892062ba08328749d467569f15afd15 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 15 Apr 2026 18:52:59 +0200 Subject: [PATCH 165/172] Make a dangerous and very unstable feature hidden. Should never be on as it ha sno doubt that option is not stable and dev not complete. A lot of side effect not yet managed must be managed first before beeing enabled. Related to #37878 --- htdocs/admin/payment.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/htdocs/admin/payment.php b/htdocs/admin/payment.php index 9b380b676b41b..19ac365c10729 100644 --- a/htdocs/admin/payment.php +++ b/htdocs/admin/payment.php @@ -253,12 +253,14 @@ print "\n"; // Allow payments on different thirdparties bills but same parent company +/* print ''; print $langs->trans("PaymentOnDifferentThirdBills"); print ''; print $form->selectyesno("FACTURE_PAYMENTS_ON_DIFFERENT_THIRDPARTIES_BILLS", $conf->global->FACTURE_PAYMENTS_ON_DIFFERENT_THIRDPARTIES_BILLS, 1); print ''; print "\n"; +*/ // Allow to group payments by mod in rapports print ''; From 32ef8e47ff411118675e6e30f09ea0861807db88 Mon Sep 17 00:00:00 2001 From: atm-lucas <121817516+atm-lucasmantegari@users.noreply.github.com> Date: Mon, 20 Apr 2026 18:29:13 +0200 Subject: [PATCH 166/172] FIX - Limit standard on price list (#37944) * Limit standard on price list * Feedback PR --- htdocs/societe/price.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/htdocs/societe/price.php b/htdocs/societe/price.php index 89b18c6fc6069..d8d00921e0366 100644 --- a/htdocs/societe/price.php +++ b/htdocs/societe/price.php @@ -35,6 +35,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; +$prodcustprice = null; if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) { require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php'; @@ -92,7 +93,7 @@ $action = 'add_customer_price'; } - if (!$error) { + if (!$error && $prodcustprice !== null) { $update_child_soc = GETPOST('updatechildprice'); // add price by customer @@ -155,7 +156,7 @@ } } - if ($action == 'delete_customer_price' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) { + if ($action == 'delete_customer_price' && $prodcustprice !== null && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) { // Delete price by customer $prodcustprice->id = GETPOSTINT('lineid'); $result = $prodcustprice->delete($user); @@ -168,7 +169,7 @@ $action = ''; } - if ($action == 'update_customer_price_confirm' && !$cancel && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) { + if ($action == 'update_customer_price_confirm' && !$cancel && $prodcustprice !== null && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) { $prodcustprice->fetch(GETPOSTINT('lineid')); $update_child_soc = GETPOST('updatechildprice'); @@ -561,7 +562,7 @@ $nbtotalofrecords = $prodcustprice->fetchAll('', '', 0, 0, $filter); } - $result = $prodcustprice->fetchAll($sortorder, $sortfield, $conf->liste_limit, $offset, $filter); + $result = $prodcustprice->fetchAll($sortorder, $sortfield, $limit, $offset, $filter); if ($result < 0) { setEventMessages($prodcustprice->error, $prodcustprice->errors, 'errors'); } From 17265d86779964136817ac8277cb97e25bdf5eab Mon Sep 17 00:00:00 2001 From: ThomasNgr-OpenDSI Date: Mon, 20 Apr 2026 22:38:56 +0200 Subject: [PATCH 167/172] FIX #GHSA-hq5j-39f9-qxcv (#37812) Co-authored-by: Laurent Destailleur --- htdocs/projet/class/project.class.php | 12 ++++++------ htdocs/projet/element.php | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/htdocs/projet/class/project.class.php b/htdocs/projet/class/project.class.php index a21203bfaff5c..20eda4c3ca3ab 100644 --- a/htdocs/projet/class/project.class.php +++ b/htdocs/projet/class/project.class.php @@ -1929,7 +1929,7 @@ public function shiftTaskDate($old_project_dt_start) public function update_element($tableName, $elementSelectId) { // phpcs:enable - $sql = "UPDATE ".MAIN_DB_PREFIX.$tableName; + $sql = "UPDATE ".MAIN_DB_PREFIX.$this->db->sanitize($tableName); if ($tableName == "actioncomm") { $sql .= " SET fk_project=".$this->id; @@ -1965,14 +1965,14 @@ public function update_element($tableName, $elementSelectId) public function remove_element($tableName, $elementSelectId, $projectfield = 'fk_projet') { // phpcs:enable - $sql = "UPDATE ".MAIN_DB_PREFIX.$tableName; + $sql = "UPDATE ".MAIN_DB_PREFIX.$this->db->sanitize($tableName); if ($tableName == "actioncomm") { - $sql .= " SET fk_project=NULL"; - $sql .= " WHERE id=".((int) $elementSelectId); + $sql .= " SET fk_project = NULL"; + $sql .= " WHERE id = ".((int) $elementSelectId); } else { - $sql .= " SET ".$projectfield."=NULL"; - $sql .= " WHERE rowid=".((int) $elementSelectId); + $sql .= " SET ".$this->db->sanitize($projectfield)." = NULL"; + $sql .= " WHERE rowid = ".((int) $elementSelectId); } dol_syslog(get_class($this)."::remove_element", LOG_DEBUG); diff --git a/htdocs/projet/element.php b/htdocs/projet/element.php index 52b82e5756da0..00a54571f09a5 100644 --- a/htdocs/projet/element.php +++ b/htdocs/projet/element.php @@ -693,7 +693,7 @@ } if ($action == "addelement") { - $tablename = GETPOST("tablename"); + $tablename = GETPOST("tablename", "aZ09"); $elementselectid = GETPOST("elementselect"); $result = $object->update_element($tablename, $elementselectid); if ($result < 0) { From 8652e9aa2430eb40f0f8e27b7bb126dea4c6f20f Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 26 Apr 2026 15:03:08 +0200 Subject: [PATCH 168/172] Fix php compatilbity --- htdocs/expedition/card.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/expedition/card.php b/htdocs/expedition/card.php index 0ddbb7c200dcd..a23e7897c65f1 100644 --- a/htdocs/expedition/card.php +++ b/htdocs/expedition/card.php @@ -1116,7 +1116,7 @@ // Document model include_once DOL_DOCUMENT_ROOT.'/core/modules/expedition/modules_expedition.php'; $list = ModelePdfExpedition::liste_modeles($db); - if (is_countable($list) && count($list) > 1) { + if (is_array($list) && count($list) > 1) { print "".$langs->trans("DefaultModel").""; print ''; print img_picto('', 'pdf', 'class="pictofixedwidth"'); From 905b52579d6eb5bc9c59cb73d5d9dc0104ce7be5 Mon Sep 17 00:00:00 2001 From: Joe Biboche Date: Tue, 28 Apr 2026 11:17:08 +0200 Subject: [PATCH 169/172] Fix: #38011 wrong line seq when generating invoice from order (#38029) Avoid reordering invoice lines in the case it's been created from an order Co-authored-by: Mathis GAVILLON --- htdocs/compta/facture/class/facture.class.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/htdocs/compta/facture/class/facture.class.php b/htdocs/compta/facture/class/facture.class.php index bdd2c697745ab..f6fac3811c46b 100644 --- a/htdocs/compta/facture/class/facture.class.php +++ b/htdocs/compta/facture/class/facture.class.php @@ -1419,6 +1419,9 @@ public function createFromOrder($object, User $user) $this->date = dol_now(); $this->source = 0; + // Avoid updating the row ranks + $this->context['createfromclone'] = 1; + $num = count($object->lines); for ($i = 0; $i < $num; $i++) { $line = new FactureLigne($this->db); From 25cfb56ec6aa49f25f4c96d2da0818b0f07b9a57 Mon Sep 17 00:00:00 2001 From: atm-lucas <121817516+atm-lucasmantegari@users.noreply.github.com> Date: Fri, 1 May 2026 23:19:04 +0200 Subject: [PATCH 170/172] Fix: normalize empty values for extrafield types mapping to INT columns (#38049) --- htdocs/core/class/commonobject.class.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index 9e9e7eceed6e7..1635e00c42991 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -6702,10 +6702,17 @@ public function insertExtraFields($trigger = '', $userused = null) switch ($attributeType) { case 'int': + case 'duration': + case 'stars': if (!is_numeric($value) && $value != '') { $this->errors[] = $langs->trans("ExtraFieldHasWrongValue", $attributeLabel); return -1; - } elseif ($value == '') { + } elseif ($value === '' || $value === false || $value === null) { + $new_array_options[$key] = null; + } + break; + case 'boolean': + if ($value === '' || $value === false || $value === null) { $new_array_options[$key] = null; } break; @@ -6796,6 +6803,10 @@ public function insertExtraFields($trigger = '', $userused = null) $new_array_options[$key] = $this->db->idate($this->array_options[$key], 'gmt'); break; case 'link': + if ($value === '' || $value === false || $value === null) { + $new_array_options[$key] = null; + break; + } $param_list = array_keys($attributeParam['options']); // 0 : ObjectName // 1 : classPath From c8ea651ca54ca1df8a0ebab33ee71e99124df817 Mon Sep 17 00:00:00 2001 From: Serhii Bondarenko <47003509+serhiilabs@users.noreply.github.com> Date: Sun, 10 May 2026 16:09:20 +0300 Subject: [PATCH 171/172] FIX #38146 Honor tpl override on supplier contacts (#38150) Signed-off-by: Serhii Bondarenko --- htdocs/fourn/commande/contact.php | 11 +++++++++-- htdocs/fourn/facture/contact.php | 11 +++++++++-- htdocs/supplier_proposal/contact.php | 11 +++++++++-- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/htdocs/fourn/commande/contact.php b/htdocs/fourn/commande/contact.php index 67b436e308ece..430c9c67432ed 100644 --- a/htdocs/fourn/commande/contact.php +++ b/htdocs/fourn/commande/contact.php @@ -5,6 +5,7 @@ * Copyright (C) 2017 Ferran Marcet * Copyright (C) 2023 Christian Foellmann * Copyright (C) 2024 Frédéric France + * Copyright (C) 2026 Serhii Bondarenko * * 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 @@ -187,8 +188,14 @@ print dol_get_fiche_end(); - // Contacts lines - include DOL_DOCUMENT_ROOT.'/core/tpl/contacts.tpl.php'; + // Contacts lines (modules that overwrite templates must declare this into descriptor) + $dirtpls = array_merge($conf->modules_parts['tpl'], array('/core/tpl')); + foreach ($dirtpls as $reldir) { + $res = @include dol_buildpath($reldir.'/contacts.tpl.php'); + if ($res) { + break; + } + } } else { // Contact not found print "ErrorRecordNotFound"; diff --git a/htdocs/fourn/facture/contact.php b/htdocs/fourn/facture/contact.php index d15646059f35a..3ff6c983bb9ec 100644 --- a/htdocs/fourn/facture/contact.php +++ b/htdocs/fourn/facture/contact.php @@ -5,6 +5,7 @@ * Copyright (C) 2017 Ferran Marcet * Copyright (C) 2021-2024 Frédéric France * Copyright (C) 2023 Christian Foellmann + * Copyright (C) 2026 Serhii Bondarenko * * 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 @@ -269,8 +270,14 @@ //print '
    '; //print '
    '; - // Contacts lines - include DOL_DOCUMENT_ROOT.'/core/tpl/contacts.tpl.php'; + // Contacts lines (modules that overwrite templates must declare this into descriptor) + $dirtpls = array_merge($conf->modules_parts['tpl'], array('/core/tpl')); + foreach ($dirtpls as $reldir) { + $res = @include dol_buildpath($reldir.'/contacts.tpl.php'); + if ($res) { + break; + } + } } else { print "ErrorRecordNotFound"; } diff --git a/htdocs/supplier_proposal/contact.php b/htdocs/supplier_proposal/contact.php index 32fa86a340f6e..8e4aa7e0a6ef8 100644 --- a/htdocs/supplier_proposal/contact.php +++ b/htdocs/supplier_proposal/contact.php @@ -6,6 +6,7 @@ * Copyright (C) 2023 Christian Foellmann * Copyright (C) 2024 Alexandre Spangaro * Copyright (C) 2024 Frédéric France + * Copyright (C) 2026 Serhii Bondarenko * * 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 @@ -189,8 +190,14 @@ print dol_get_fiche_end(); - // Contacts lines - include DOL_DOCUMENT_ROOT.'/core/tpl/contacts.tpl.php'; + // Contacts lines (modules that overwrite templates must declare this into descriptor) + $dirtpls = array_merge($conf->modules_parts['tpl'], array('/core/tpl')); + foreach ($dirtpls as $reldir) { + $res = @include dol_buildpath($reldir.'/contacts.tpl.php'); + if ($res) { + break; + } + } } else { // Contact not found print "ErrorRecordNotFound"; From cd5c97c72b59e948fa2a0d4efaf6d8f809d6d94c Mon Sep 17 00:00:00 2001 From: ATM-Lucas Date: Wed, 13 May 2026 10:43:06 +0200 Subject: [PATCH 172/172] FIX : DA028190 - Ajout Cyan_mpprotection qui ajoute une ligne d'accompte dans le total du pdf --- Changelog_AMFISA.md | 6 + .../doc/pdf_cyan_mpprotection.modules.php | 2176 +++++++++++++++++ 2 files changed, 2182 insertions(+) create mode 100644 Changelog_AMFISA.md create mode 100644 htdocs/core/modules/propale/doc/pdf_cyan_mpprotection.modules.php diff --git a/Changelog_AMFISA.md b/Changelog_AMFISA.md new file mode 100644 index 0000000000000..cd59afea525ad --- /dev/null +++ b/Changelog_AMFISA.md @@ -0,0 +1,6 @@ +-------------------------------------------------------------- +AMFISA Dolibarr ChangeLog +-------------------------------------------------------------- + +FIX : DA028190 - Ajout Cyan_mpprotection qui ajoute une ligne d'accompte dans le total du pdf +Taux d'accompte modification si besoin via : PROPALE_MPPROTECTION_DEPOSIT_RATE diff --git a/htdocs/core/modules/propale/doc/pdf_cyan_mpprotection.modules.php b/htdocs/core/modules/propale/doc/pdf_cyan_mpprotection.modules.php new file mode 100644 index 0000000000000..d5a00b56f716e --- /dev/null +++ b/htdocs/core/modules/propale/doc/pdf_cyan_mpprotection.modules.php @@ -0,0 +1,2176 @@ + + * Copyright (C) 2005-2012 Regis Houssin + * Copyright (C) 2008 Raphael Bertrand + * Copyright (C) 2010-2015 Juanjo Menent + * Copyright (C) 2012 Christophe Battarel + * Copyright (C) 2012 Cedric Salvador + * Copyright (C) 2015 Marcos García + * Copyright (C) 2017 Ferran Marcet + * Copyright (C) 2021-2024 Anthony Berton + * Copyright (C) 2018-2024 Frédéric France + * Copyright (C) 2024 MDW + * Copyright (C) 2024 Nick Fragoulis + * Copyright (C) 2024 Alexandre Spangaro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * or see https://www.gnu.org/ + */ + +/** + * \file htdocs/core/modules/propale/doc/pdf_cyan_mpprotection.modules.php + * \ingroup propale + * \brief File of Class to generate PDF proposal with Cyan MP Protection template + */ +require_once DOL_DOCUMENT_ROOT.'/core/modules/propale/modules_propale.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php'; + + +/** + * Class to generate PDF proposal Cyan MP Protection + */ +class pdf_cyan_mpprotection extends ModelePDFPropales +{ + /** + * @var DoliDB Database handler + */ + public $db; + + /** + * @var string model name + */ + public $name; + + /** + * @var string model description (short text) + */ + public $description; + + /** + * @var int Save the name of generated file as the main doc when generating a doc with this template + */ + public $update_main_doc_field; + + /** + * @var string document type + */ + public $type; + + /** + * Dolibarr version of the loaded document + * @var string Version, possible values are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated' or a version string like 'x.y.z'''|'development'|'dolibarr'|'experimental' + */ + public $version = 'dolibarr'; + + /** + * @var array Array of document table columns + */ + public $cols; + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + public function __construct($db) + { + global $langs, $mysoc; + + // Translations + $langs->loadLangs(array("main", "bills")); + + $this->db = $db; + $this->name = "cyan_mpprotection"; + $this->description = $langs->trans('DocModelCyanDescription').' (MP Protection)'; + $this->update_main_doc_field = 1; // Save the name of generated file as the main doc when generating a doc with this template + + // Dimension page + $this->type = 'pdf'; + $formatarray = pdf_getFormat(); + $this->page_largeur = $formatarray['width']; + $this->page_hauteur = $formatarray['height']; + $this->format = array($this->page_largeur, $this->page_hauteur); + $this->marge_gauche = getDolGlobalInt('MAIN_PDF_MARGIN_LEFT', 10); + $this->marge_droite = getDolGlobalInt('MAIN_PDF_MARGIN_RIGHT', 10); + $this->marge_haute = getDolGlobalInt('MAIN_PDF_MARGIN_TOP', 10); + $this->marge_basse = getDolGlobalInt('MAIN_PDF_MARGIN_BOTTOM', 10); + $this->corner_radius = getDolGlobalInt('MAIN_PDF_FRAME_CORNER_RADIUS', 0); + $this->option_logo = 1; // Display logo + $this->option_tva = 1; // Manage the vat option FACTURE_TVAOPTION + $this->option_modereg = 1; // Display payment mode + $this->option_condreg = 1; // Display payment terms + $this->option_multilang = 1; // Available in several languages + $this->option_escompte = 0; // Displays if there has been a discount + $this->option_credit_note = 0; // Support credit notes + $this->option_freetext = 1; // Support add of a personalised text + $this->option_draft_watermark = 1; // Support add of a watermark on drafts + $this->watermark = ''; + + // Define position of columns + $this->posxdesc = $this->marge_gauche + 1; // used for notes and other stuff + + + $this->tabTitleHeight = 5; // default height + + // Use new system for position of columns, view $this->defineColumnField() + + $this->tva = array(); + $this->tva_array = array(); + $this->localtax1 = array(); + $this->localtax2 = array(); + $this->atleastoneratenotnull = 0; + $this->atleastonediscount = 0; + + if ($mysoc === null) { + dol_syslog(get_class($this).'::__construct() Global $mysoc should not be null.'. getCallerInfoString(), LOG_ERR); + return; + } + + // Get source company + $this->emetteur = $mysoc; + if (empty($this->emetteur->country_code)) { + $this->emetteur->country_code = substr($langs->defaultlang, -2); // By default, if was not defined + } + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps + /** + * Function to build pdf onto disk + * + * @param Propal $object Object to generate + * @param Translate $outputlangs Lang output object + * @param string $srctemplatepath Full path of source filename for generator using a template file + * @param int<0,1> $hidedetails Do not show line details + * @param int<0,1> $hidedesc Do not show desc + * @param int<0,1> $hideref Do not show ref + * @return int<-1,1> 1 if OK, <=0 if KO + */ + public function write_file($object, $outputlangs, $srctemplatepath = '', $hidedetails = 0, $hidedesc = 0, $hideref = 0) + { + // phpcs:enable + global $user, $langs, $conf, $mysoc, $db, $hookmanager, $nblines; + + dol_syslog("write_file outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null')); + + if (!is_object($outputlangs)) { + $outputlangs = $langs; + } + // For backward compatibility with FPDF, force output charset to ISO, because FPDF expect text to be encoded in ISO + if (getDolGlobalString('MAIN_USE_FPDF')) { + $outputlangs->charset_output = 'ISO-8859-1'; + } + + // Load translation files required by page + $langfiles = array("main", "dict", "companies", "bills", "products", "propal", "compta"); + $outputlangs->loadLangs($langfiles); + + // Show Draft Watermark + if ($object->status == $object::STATUS_DRAFT && getDolGlobalString('PROPALE_DRAFT_WATERMARK')) { + $this->watermark = getDolGlobalString('PROPALE_DRAFT_WATERMARK'); + } + + global $outputlangsbis; + $outputlangsbis = null; + if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE') && $outputlangs->defaultlang != getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')) { + $outputlangsbis = new Translate('', $conf); + $outputlangsbis->setDefaultLang(getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')); + $outputlangsbis->loadLangs($langfiles); + } + + $nblines = count($object->lines); + + $hidetop = getDolGlobalInt('MAIN_PDF_DISABLE_COL_HEAD_TITLE'); + + // Loop on each lines to detect if there is at least one image to show + $realpatharray = array(); + $this->atleastonephoto = false; + if (getDolGlobalString('MAIN_GENERATE_PROPOSALS_WITH_PICTURE')) { + $objphoto = new Product($this->db); + + for ($i = 0; $i < $nblines; $i++) { + if (empty($object->lines[$i]->fk_product)) { + continue; + } + + $objphoto->fetch($object->lines[$i]->fk_product); + //var_dump($objphoto->ref);exit; + $pdir = array(); + if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) { + $pdir[0] = get_exdir($objphoto->id, 2, 0, 0, $objphoto, 'product').$objphoto->id."/photos/"; + $pdir[1] = get_exdir(0, 0, 0, 0, $objphoto, 'product').dol_sanitizeFileName($objphoto->ref).'/'; + } else { + $pdir[0] = get_exdir(0, 0, 0, 0, $objphoto, 'product'); // default + $pdir[1] = get_exdir($objphoto->id, 2, 0, 0, $objphoto, 'product').$objphoto->id."/photos/"; // alternative + } + + $realpath = ''; + $arephoto = false; + foreach ($pdir as $midir) { + if (!$arephoto) { + if ($conf->entity != $objphoto->entity) { + $dir = $conf->product->multidir_output[$objphoto->entity].'/'.$midir; //Check repertories of current entities + } else { + $dir = $conf->product->dir_output.'/'.$midir; //Check repertory of the current product + } + + foreach ($objphoto->liste_photos($dir, 1) as $key => $obj) { + if (!getDolGlobalInt('CAT_HIGH_QUALITY_IMAGES')) { // If CAT_HIGH_QUALITY_IMAGES not defined, we use thumb if defined and then original photo + if ($obj['photo_vignette']) { + $filename = $obj['photo_vignette']; + } else { + $filename = $obj['photo']; + } + } else { + $filename = $obj['photo']; + } + + $realpath = $dir.$filename; + $arephoto = true; + $this->atleastonephoto = true; + } + } + } + + if ($realpath && $arephoto) { + $realpatharray[$i] = $realpath; + } + } + } + + if (count($realpatharray) == 0) { + $this->posxpicture = $this->posxtva; + } + + if ($conf->propal->multidir_output[$conf->entity]) { + $object->fetch_thirdparty(); + + $deja_regle = 0; + + // Definition of $dir and $file + if ($object->specimen) { + $dir = $conf->propal->multidir_output[$conf->entity]; + $file = $dir."/SPECIMEN.pdf"; + } else { + $objectref = dol_sanitizeFileName($object->ref); + $dir = $conf->propal->multidir_output[$object->entity]."/".$objectref; + $file = $dir."/".$objectref.".pdf"; + } + + if (!file_exists($dir)) { + if (dol_mkdir($dir) < 0) { + $this->error = $langs->transnoentities("ErrorCanNotCreateDir", $dir); + return 0; + } + } + + if (file_exists($dir)) { + // Add pdfgeneration hook + if (!is_object($hookmanager)) { + include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php'; + $hookmanager = new HookManager($this->db); + } + $hookmanager->initHooks(array('pdfgeneration')); + $parameters = array('file' => $file, 'object' => $object, 'outputlangs' => $outputlangs); + global $action; + $reshook = $hookmanager->executeHooks('beforePDFCreation', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks + + // Set nblines with the new content of lines after hook + $nblines = count($object->lines); + //$nbpayments = count($object->getListOfPayments()); + + // Create pdf instance + $pdf = pdf_getInstance($this->format); + $default_font_size = pdf_getPDFFontSize($outputlangs); // Must be after pdf_getInstance + $pdf->SetAutoPageBreak(1, 0); + + if (class_exists('TCPDF')) { + $pdf->setPrintHeader(false); + $pdf->setPrintFooter(false); + } + $pdf->SetFont(pdf_getPDFFont($outputlangs)); + // Set path to the background PDF File + if (getDolGlobalString('MAIN_ADD_PDF_BACKGROUND')) { + $logodir = $conf->mycompany->dir_output; + if (!empty($conf->mycompany->multidir_output[$object->entity])) { + $logodir = $conf->mycompany->multidir_output[$object->entity]; + } + $pagecount = $pdf->setSourceFile($logodir.'/' . getDolGlobalString('MAIN_ADD_PDF_BACKGROUND')); + $tplidx = $pdf->importPage(1); + } + + $pdf->Open(); + $pagenb = 0; + $pdf->SetDrawColor(128, 128, 128); + + $pdf->SetTitle($outputlangs->convToOutputCharset($object->ref)); + $pdf->SetSubject($outputlangs->transnoentities("PdfCommercialProposalTitle")); + $pdf->SetCreator("Dolibarr ".DOL_VERSION); + $pdf->SetAuthor($outputlangs->convToOutputCharset($user->getFullName($outputlangs))); + $pdf->SetKeyWords($outputlangs->convToOutputCharset($object->ref)." ".$outputlangs->transnoentities("PdfCommercialProposalTitle")." ".$outputlangs->convToOutputCharset($object->thirdparty->name)); + if (getDolGlobalString('MAIN_DISABLE_PDF_COMPRESSION')) { + $pdf->SetCompression(false); + } + + // @phan-suppress-next-line PhanPluginSuspiciousParamOrder + $pdf->SetMargins($this->marge_gauche, $this->marge_haute, $this->marge_droite); // Left, Top, Right + + // Set $this->atleastonediscount if you have at least one discount + for ($i = 0; $i < $nblines; $i++) { + if ($object->lines[$i]->remise_percent) { + $this->atleastonediscount++; + } + } + + + // New page + $pdf->AddPage(); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + $pagenb++; + + $heightforinfotot = 40; // Height reserved to output the info and total part + $heightforsignature = !getDolGlobalString('PROPAL_DISABLE_SIGNATURE') ? (pdfGetHeightForHtmlContent($pdf, $outputlangs->transnoentities("ProposalCustomerSignature")) + 10) : 0; + $heightforfreetext = getDolGlobalInt('MAIN_PDF_FREETEXT_HEIGHT', 5); // Height reserved to output the free text on last page + $heightforfooter = $this->marge_basse + (!getDolGlobalString('MAIN_GENERATE_DOCUMENTS_SHOW_FOOT_DETAILS') ? 12 : 22); // Height reserved to output the footer (value include bottom margin) + //print $heightforinfotot + $heightforsignature + $heightforfreetext + $heightforfooter;exit; + + $top_shift = $this->_pagehead($pdf, $object, 1, $outputlangs, $outputlangsbis); + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->MultiCell(0, 3, ''); // Set interline to 3 + $pdf->SetTextColor(0, 0, 0); + + + $tab_top = 90 + $top_shift; + $tab_top_newpage = (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD') ? 42 + $top_shift : 10); + if (!$hidetop && getDolGlobalInt('MAIN_PDF_ENABLE_COL_HEAD_TITLE_REPEAT')) { + // TODO : make this hidden conf the default behavior for each PDF when each PDF managed this new Display + $tab_top_newpage+= $this->tabTitleHeight; + } + + $nexY = $tab_top; + + // Incoterm + $height_incoterms = 0; + if (isModEnabled('incoterm')) { + $desc_incoterms = $object->getIncotermsForPDF(); + if ($desc_incoterms) { + $tab_top -= 2; + + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->writeHTMLCell(190, 3, $this->posxdesc - 1, $tab_top - 1, dol_htmlentitiesbr($desc_incoterms), 0, 1); + $nexY = max($pdf->GetY(), $nexY); + $height_incoterms = $nexY - $tab_top; + + // Rect takes a length in 3rd parameter + $pdf->SetDrawColor(192, 192, 192); + $pdf->RoundedRect($this->marge_gauche, $tab_top - 1, $this->page_largeur - $this->marge_gauche - $this->marge_droite, $height_incoterms + 3, $this->corner_radius, '1234', 'D'); + + $tab_top = $nexY + 6; + $height_incoterms += 4; + } + } + + // Displays notes + $notetoshow = empty($object->note_public) ? '' : $object->note_public; + if (getDolGlobalString('MAIN_ADD_SALE_REP_SIGNATURE_IN_NOTE')) { + // Get first sale rep + if (is_object($object->thirdparty)) { + $salereparray = $object->thirdparty->getSalesRepresentatives($user); + $salerepobj = new User($this->db); + $salerepobj->fetch($salereparray[0]['id']); + if (!empty($salerepobj->signature)) { + $notetoshow = dol_concatdesc($notetoshow, $salerepobj->signature); + } + } + } + + // Extrafields in note + $extranote = $this->getExtrafieldsInHtml($object, $outputlangs); + if (!empty($extranote)) { + $notetoshow = dol_concatdesc($notetoshow, $extranote); + } + + if (getDolGlobalString('MAIN_ADD_CREATOR_IN_NOTE') && $object->user_author_id > 0) { + $tmpuser = new User($this->db); + $tmpuser->fetch($object->user_author_id); + + $creator_info = $langs->trans("CaseFollowedBy").' '.$tmpuser->getFullName($langs); + if ($tmpuser->email) { + $creator_info .= ', '.$langs->trans("EMail").': '.$tmpuser->email; + } + if ($tmpuser->office_phone) { + $creator_info .= ', '.$langs->trans("Phone").': '.$tmpuser->office_phone; + } + + $notetoshow = dol_concatdesc($notetoshow, $creator_info); + } + + $tab_height = $this->page_hauteur - $tab_top_newpage - $heightforinfotot - $heightforfreetext - $heightforsignature - $heightforfooter; + + $pagenb = $pdf->getPage(); + if ($notetoshow) { + $tab_top -= 2; + + $tab_width = $this->page_largeur - $this->marge_gauche - $this->marge_droite; + $pageposbeforenote = $pagenb; + + $substitutionarray = pdf_getSubstitutionArray($outputlangs, null, $object); + complete_substitutions_array($substitutionarray, $outputlangs, $object); + $notetoshow = make_substitutions($notetoshow, $substitutionarray, $outputlangs); + $notetoshow = convertBackOfficeMediasLinksToPublicLinks($notetoshow); + + $pdf->startTransaction(); + + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->writeHTMLCell(190, 3, $this->posxdesc - 1, $tab_top, dol_htmlentitiesbr($notetoshow), 0, 1); + // Description + $pageposafternote = $pdf->getPage(); + $posyafter = $pdf->GetY(); + + if ($pageposafternote > $pageposbeforenote) { + $pdf->rollbackTransaction(true); + + // prepare pages to receive notes + while ($pagenb < $pageposafternote) { + $pdf->AddPage(); + $pagenb++; + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) { + $this->_pagehead($pdf, $object, 0, $outputlangs); + } + // $this->_pagefoot($pdf,$object,$outputlangs,1); + $pdf->setTopMargin($tab_top_newpage); + // The only function to edit the bottom margin of current page to set it. + $pdf->setPageOrientation('', 1, $heightforfooter + $heightforfreetext); + } + + // back to start + $pdf->setPage($pageposbeforenote); + $pdf->setPageOrientation('', 1, $heightforfooter + $heightforfreetext); + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->writeHTMLCell(190, 3, $this->posxdesc - 1, $tab_top, dol_htmlentitiesbr($notetoshow), 0, 1); + $pageposafternote = $pdf->getPage(); + + $posyafter = $pdf->GetY(); + + if ($posyafter > ($this->page_hauteur - ($heightforfooter + $heightforfreetext + 20))) { // There is no space left for total+free text + $pdf->AddPage('', '', true); + $pagenb++; + $pageposafternote++; + $pdf->setPage($pageposafternote); + $pdf->setTopMargin($tab_top_newpage); + // The only function to edit the bottom margin of current page to set it. + $pdf->setPageOrientation('', 1, $heightforfooter + $heightforfreetext); + //$posyafter = $tab_top_newpage; + } + + + // apply note frame to previous pages + $i = $pageposbeforenote; + while ($i < $pageposafternote) { + $pdf->setPage($i); + + + $pdf->SetDrawColor(128, 128, 128); + // Draw note frame + if ($i > $pageposbeforenote) { + $height_note = $this->page_hauteur - ($tab_top_newpage + $heightforfooter); + $pdf->RoundedRect($this->marge_gauche, $tab_top_newpage - 1, $tab_width, $height_note + 2, $this->corner_radius, '1234', 'D'); + } else { + $height_note = $this->page_hauteur - ($tab_top + $heightforfooter); + $pdf->RoundedRect($this->marge_gauche, $tab_top - 1, $tab_width, $height_note + 2, $this->corner_radius, '1234', 'D'); + } + + // Add footer + $pdf->setPageOrientation('', 1, 0); // The only function to edit the bottom margin of current page to set it. + $this->_pagefoot($pdf, $object, $outputlangs, 1); + + $i++; + } + + // apply note frame to last page + $pdf->setPage($pageposafternote); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) { + $this->_pagehead($pdf, $object, 0, $outputlangs); + } + $height_note = $posyafter - $tab_top_newpage; + $pdf->RoundedRect($this->marge_gauche, $tab_top_newpage - 1, $tab_width, $height_note + 2, $this->corner_radius, '1234', 'D'); + } else { + // No pagebreak + $pdf->commitTransaction(); + $posyafter = $pdf->GetY(); + $height_note = $posyafter - $tab_top; + $pdf->RoundedRect($this->marge_gauche, $tab_top - 1, $tab_width, $height_note + 2, $this->corner_radius, '1234', 'D'); + + + if ($posyafter > ($this->page_hauteur - ($heightforfooter + $heightforfreetext + 20))) { + // not enough space, need to add page + $pdf->AddPage('', '', true); + $pagenb++; + $pageposafternote++; + $pdf->setPage($pageposafternote); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) { + $this->_pagehead($pdf, $object, 0, $outputlangs); + } + + $posyafter = $tab_top_newpage; + } + } + $tab_height -= $height_note; + $tab_top = $posyafter + 6; + } else { + $height_note = 0; + } + + // Use new auto column system + $this->prepareArrayColumnField($object, $outputlangs, $hidedetails, $hidedesc, $hideref); + + // Table simulation to know the height of the title line + $pdf->startTransaction(); + $this->pdfTabTitles($pdf, $tab_top, $tab_height, $outputlangs, $hidetop); + $pdf->rollbackTransaction(true); + + $nexY = $tab_top + $this->tabTitleHeight; + + // Loop on each lines + $pageposbeforeprintlines = $pdf->getPage(); + $pagenb = $pageposbeforeprintlines; + + for ($i = 0; $i < $nblines; $i++) { + $linePosition = $i + 1; + $curY = $nexY; + + // in First Check line page break and add page if needed + if (isset($object->lines[$i]->pagebreak) && $object->lines[$i]->pagebreak) { + // New page + $pdf->AddPage(); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + + $pdf->setPage($pdf->getNumPages()); + $nexY = $tab_top_newpage; + } + + $this->resetAfterColsLinePositionsData($nexY, $pdf->getPage()); + + $pdf->SetFont('', '', $default_font_size - 1); // Into loop to work with multipage + $pdf->SetTextColor(0, 0, 0); + + // Define size of image if we need it + $imglinesize = array(); + if (!empty($realpatharray[$i])) { + $imglinesize = pdf_getSizeForImage($realpatharray[$i]); + } + + $pdf->setTopMargin($tab_top_newpage); + $pdf->setPageOrientation('', 1, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + $pageposbefore = $pdf->getPage(); + $curYBefore = $curY; + + // Allows data in the first page if description is long enough to break in multiples pages + $showpricebeforepagebreak = getDolGlobalInt('MAIN_PDF_DATA_ON_FIRST_PAGE'); + + $posYAfterImage = 0; + + if ($this->getColumnStatus('photo')) { + // We start with Photo of product line + $imageTopMargin = 1; + if (isset($imglinesize['width']) && isset($imglinesize['height']) && ($curY + $imageTopMargin + $imglinesize['height']) > ($this->page_hauteur - $heightforfooter)) { // If photo too high, we moved completely on new page + $pdf->AddPage('', '', true); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + $pdf->setPage($pageposbefore + 1); + $pdf->setPageOrientation('', 1, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + $curY = $tab_top_newpage; + $showpricebeforepagebreak = 0; + } + + $pdf->setPageOrientation('', 0, $heightforfooter + $heightforfreetext); // The only function to edit the bottom margin of current page to set it. + if (!empty($this->cols['photo']) && isset($imglinesize['width']) && isset($imglinesize['height'])) { + $pdf->Image($realpatharray[$i], $this->getColumnContentXStart('photo'), $curY + $imageTopMargin, $imglinesize['width'], $imglinesize['height'], '', '', '', 2, 300); // Use 300 dpi + // $pdf->Image does not increase value return by getY, so we save it manually + $posYAfterImage = $curY + $imglinesize['height']; + + $this->setAfterColsLinePositionsData('photo', $posYAfterImage, $pdf->getPage()); + } + } + + // restore Page orientation for text + $pdf->setPageOrientation('', 1, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + + if ($this->getColumnStatus('desc')) { + $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); + $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); + } + + $afterPosData = $this->getMaxAfterColsLinePositionsData(); + $pdf->setPage($pageposbefore); + $pdf->setTopMargin($this->marge_haute); + $curY = $curYBefore; + $pdf->setPageOrientation('', 0, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + + + // We suppose that a too long description or photo were moved completely on next page + if ($afterPosData['page'] > $pageposbefore && (empty($showpricebeforepagebreak) || ($curY + 4) > ($this->page_hauteur - $heightforfooter))) { + $pdf->setPage($afterPosData['page']); + $curY = $tab_top_newpage; + } + + $pdf->SetFont('', '', $default_font_size - 1); // We reposition the default font + + + // # of line + if ($this->getColumnStatus('position')) { + $this->printStdColumnContent($pdf, $curY, 'position', strval($linePosition)); + } + + // VAT Rate + if ($this->getColumnStatus('vat')) { + $vat_rate = pdf_getlinevatrate($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'vat', $vat_rate); + } + + // Unit price before discount + if ($this->getColumnStatus('subprice')) { + $up_excl_tax = pdf_getlineupexcltax($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'subprice', $up_excl_tax); + } + + // Quantity + // Enough for 6 chars + if ($this->getColumnStatus('qty')) { + $qty = pdf_getlineqty($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'qty', $qty); + } + + + // Unit + if ($this->getColumnStatus('unit')) { + $unit = pdf_getlineunit($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'unit', $unit); + } + + // Discount on line + if ($this->getColumnStatus('discount') && $object->lines[$i]->remise_percent) { + $remise_percent = pdf_getlineremisepercent($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'discount', $remise_percent); + } + + // Total excl tax line (HT) + if ($this->getColumnStatus('totalexcltax')) { + $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); + } + + // Total with tax line (TTC) + if ($this->getColumnStatus('totalincltax')) { + $total_incl_tax = pdf_getlinetotalwithtax($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'totalincltax', $total_incl_tax); + } + + // Extrafields + if (!empty($object->lines[$i]->array_options)) { + foreach ($object->lines[$i]->array_options as $extrafieldColKey => $extrafieldValue) { + if ($this->getColumnStatus($extrafieldColKey)) { + $extrafieldValue = $this->getExtrafieldContent($object->lines[$i], $extrafieldColKey, $outputlangs); + $this->printStdColumnContent($pdf, $curY, $extrafieldColKey, $extrafieldValue); + + $this->setAfterColsLinePositionsData('options_'.$extrafieldColKey, $pdf->GetY(), $pdf->getPage()); + } + } + } + + $afterPosData = $this->getMaxAfterColsLinePositionsData(); + $parameters = array( + 'object' => $object, + 'i' => $i, + 'pdf' => & $pdf, + 'curY' => & $curY, + 'nexY' => & $afterPosData['y'], // for backward module hook compatibility Y will be accessible by $object->getMaxAfterColsLinePositionsData() + 'outputlangs' => $outputlangs, + 'hidedetails' => $hidedetails + ); + $reshook = $hookmanager->executeHooks('printPDFline', $parameters, $this); // Note that $object may have been modified by hook + + + // Collection of totals by value of vat in $this->tva["rate"] = total_tva + if (isModEnabled("multicurrency") && $object->multicurrency_tx != 1) { + $tvaligne = $object->lines[$i]->multicurrency_total_tva; + } else { + $tvaligne = $object->lines[$i]->total_tva; + } + + $localtax1ligne = $object->lines[$i]->total_localtax1; + $localtax2ligne = $object->lines[$i]->total_localtax2; + $localtax1_rate = $object->lines[$i]->localtax1_tx; + $localtax2_rate = $object->lines[$i]->localtax2_tx; + $localtax1_type = $object->lines[$i]->localtax1_type; + $localtax2_type = $object->lines[$i]->localtax2_type; + + // TODO remise_percent is an obsolete field for object parent + /*if ($object->remise_percent) { + $tvaligne -= ($tvaligne * $object->remise_percent) / 100; + } + if ($object->remise_percent) { + $localtax1ligne -= ($localtax1ligne * $object->remise_percent) / 100; + } + if ($object->remise_percent) { + $localtax2ligne -= ($localtax2ligne * $object->remise_percent) / 100; + }*/ + + $vatrate = (string) $object->lines[$i]->tva_tx; + + // Retrieve type from database for backward compatibility with old records + if ((!isset($localtax1_type) || $localtax1_type == '' || !isset($localtax2_type) || $localtax2_type == '') // if tax type not defined + && (!empty($localtax1_rate) || !empty($localtax2_rate))) { // and there is local tax + $localtaxtmp_array = getLocalTaxesFromRate($vatrate, 0, $object->thirdparty, $mysoc); + $localtax1_type = isset($localtaxtmp_array[0]) ? $localtaxtmp_array[0] : ''; + $localtax2_type = isset($localtaxtmp_array[2]) ? $localtaxtmp_array[2] : ''; + } + + // retrieve global local tax + if ($localtax1_type && $localtax1ligne != 0) { + if (empty($this->localtax1[$localtax1_type][$localtax1_rate])) { + $this->localtax1[$localtax1_type][$localtax1_rate] = $localtax1ligne; + } else { + $this->localtax1[$localtax1_type][$localtax1_rate] += $localtax1ligne; + } + } + if ($localtax2_type && $localtax2ligne != 0) { + if (empty($this->localtax2[$localtax2_type][$localtax2_rate])) { + $this->localtax2[$localtax2_type][$localtax2_rate] = $localtax2ligne; + } else { + $this->localtax2[$localtax2_type][$localtax2_rate] += $localtax2ligne; + } + } + + if (($object->lines[$i]->info_bits & 0x01) == 0x01) { + $vatrate .= '*'; + } + + // Fill $this->tva and $this->tva_array + if (!isset($this->tva[$vatrate])) { + $this->tva[$vatrate] = 0; + } + $this->tva[$vatrate] += $tvaligne; + $vatcode = $object->lines[$i]->vat_src_code; + if (empty($this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'])) { + $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'] = 0; + } + $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')] = array('vatrate' => $vatrate, 'vatcode' => $vatcode, 'amount' => $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'] + $tvaligne); + + + + $afterPosData = $this->getMaxAfterColsLinePositionsData(); + $pdf->setPage($afterPosData['page']); + $nexY = $afterPosData['y']; + + // Add line + if (getDolGlobalString('MAIN_PDF_DASH_BETWEEN_LINES') && $i < ($nblines - 1) && $afterPosData['y'] < $this->page_hauteur - $heightforfooter - 5) { + $pdf->SetLineStyle(array('dash' => '1,1', 'color' => array(80, 80, 80))); + //$pdf->SetDrawColor(190,190,200); + $pdf->line($this->marge_gauche, $nexY + 1, $this->page_largeur - $this->marge_droite, $nexY + 1); + $pdf->SetLineStyle(array('dash' => 0)); + } + + $nexY += 2; // Add space between lines + } + + + // Add last page for document footer if there are not enough size left + $afterPosData = $this->getMaxAfterColsLinePositionsData(); + if (isset($afterPosData['y']) && $afterPosData['y'] > $this->page_hauteur - ($heightforfooter + $heightforfreetext + $heightforsignature + $heightforinfotot) ) { + $pdf->AddPage(); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + $pagenb++; + $pdf->setPage($pagenb); + } + + // Draw table frames and columns borders + $drawTabNumbPage = $pdf->getNumPages(); + for ($i=$pageposbeforeprintlines; $i<=$drawTabNumbPage; $i++) { + $pdf->setPage($i); + // reset page orientation each loop to override it if it was changed + $pdf->setPageOrientation('', 0, 0); // The only function to edit the bottom margin of current page to set it. + + $drawTabHideTop = $hidetop; + $drawTabTop = $tab_top_newpage; + $drawTabBottom = $this->page_hauteur - $heightforfooter; + $hideBottom = 0; // TODO understand why it change to 1 or 0 during process + + if ($i == $pageposbeforeprintlines) { + // first page need to start after notes + $drawTabTop = $tab_top; + } elseif (!$drawTabHideTop) { + if (getDolGlobalInt('MAIN_PDF_ENABLE_COL_HEAD_TITLE_REPEAT')) { + $drawTabTop-= $this->tabTitleHeight; + } else { + $drawTabHideTop = 1; + } + } + + // last page need to include document footer + if ($i == $pdf->getNumPages()) { + // remove document footer height to tab bottom position + $drawTabBottom-= $heightforsignature + $heightforfreetext + $heightforinfotot; + } + + $drawTabHeight = $drawTabBottom - $drawTabTop; + $this->_tableau($pdf, $drawTabTop, $drawTabHeight, 0, $outputlangs, $drawTabHideTop, $hideBottom, $object->multicurrency_code, $outputlangsbis); + + $hideFreeText = $i != $pdf->getNumPages() ? 1 : 0; // Display free text only in last page + $this->_pagefoot($pdf, $object, $outputlangs, $hideFreeText); + + $pdf->setPage($i); // in case of _pagefoot or _tableau change it + + // reset page orientation each loop to override it if it was changed by _pagefoot or _tableau change it + $pdf->setPageOrientation('', 1, 0); // The only function to edit the bottom margin of current page to set it. + + // Don't print head on first page ($pageposbeforeprintlines) because already added previously + if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD') && $i != $pageposbeforeprintlines) { + $this->_pagehead($pdf, $object, 0, $outputlangs); + } + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + } + + // reset text color before print footers + $pdf->SetTextColor(0, 0, 0); + + $pdf->setPage($pdf->getNumPages()); + + $bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforsignature - $heightforfooter + 1; + + // Display infos area + $posy = $this->drawInfoTable($pdf, $object, $bottomlasttab, $outputlangs); + + // Display total zone + $posy = $this->drawTotalTable($pdf, $object, 0, $bottomlasttab, $outputlangs); + + // Customer signature area + if (!getDolGlobalString('PROPAL_DISABLE_SIGNATURE')) { + $posy = $this->drawSignatureArea($pdf, $object, $posy, $outputlangs); + } + + // Add number of pages in footer + if (method_exists($pdf, 'AliasNbPages')) { + $pdf->AliasNbPages(); // @phan-suppress-current-line PhanUndeclaredMethod + } + + // Add terms to sale + if (!empty($mysoc->termsofsale) && getDolGlobalInt('MAIN_PDF_ADD_TERMSOFSALE_PROPAL')) { + $termsofsale = $conf->mycompany->dir_output.'/'.$mysoc->termsofsale; + if (!empty($conf->mycompany->multidir_output[$object->entity])) { + $termsofsale = $conf->mycompany->multidir_output[$object->entity].'/'.$mysoc->termsofsale; + } + if (file_exists($termsofsale) && is_readable($termsofsale)) { + $pagecount = $pdf->setSourceFile($termsofsale); + for ($i = 1; $i <= $pagecount; $i++) { + $tplIdx = $pdf->importPage($i); + if ($tplIdx!==false) { + $s = $pdf->getTemplatesize($tplIdx); + $pdf->AddPage($s['h'] > $s['w'] ? 'P' : 'L'); + $pdf->useTemplate($tplIdx); + } else { + setEventMessages(null, array($termsofsale.' cannot be added, probably protected PDF'), 'warnings'); + } + } + } + } + + //If propal merge product PDF is active + if (getDolGlobalString('PRODUIT_PDF_MERGE_PROPAL')) { + require_once DOL_DOCUMENT_ROOT.'/product/class/propalmergepdfproduct.class.php'; + + $already_merged = array(); + foreach ($object->lines as $line) { + if (!empty($line->fk_product) && !(in_array($line->fk_product, $already_merged))) { + // Find the desired PDF + $filetomerge = new Propalmergepdfproduct($this->db); + + if (getDolGlobalInt('MAIN_MULTILANGS')) { + $filetomerge->fetch_by_product($line->fk_product, $outputlangs->defaultlang); + } else { + $filetomerge->fetch_by_product($line->fk_product); + } + + $already_merged[] = $line->fk_product; + + $product = new Product($this->db); + $product->fetch($line->fk_product); + + if ($product->entity != $conf->entity) { + $entity_product_file = $product->entity; + } else { + $entity_product_file = $conf->entity; + } + + // If PDF is selected and file is not empty + if (count($filetomerge->lines) > 0) { + foreach ($filetomerge->lines as $linefile) { + if (!empty($linefile->id) && !empty($linefile->file_name)) { + if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) { + if (isModEnabled("product")) { + $filetomerge_dir = $conf->product->multidir_output[$entity_product_file].'/'.get_exdir($product->id, 2, 0, 0, $product, 'product').$product->id."/photos"; + } elseif (isModEnabled("service")) { + $filetomerge_dir = $conf->service->multidir_output[$entity_product_file].'/'.get_exdir($product->id, 2, 0, 0, $product, 'product').$product->id."/photos"; + } + } else { + if (isModEnabled("product")) { + $filetomerge_dir = $conf->product->multidir_output[$entity_product_file].'/'.get_exdir(0, 0, 0, 0, $product, 'product'); + } elseif (isModEnabled("service")) { + $filetomerge_dir = $conf->service->multidir_output[$entity_product_file].'/'.get_exdir(0, 0, 0, 0, $product, 'product'); + } + } + + dol_syslog(get_class($this).':: upload_dir='.$filetomerge_dir, LOG_DEBUG); + + $infile = $filetomerge_dir.'/'.$linefile->file_name; + if (file_exists($infile) && is_readable($infile)) { + $pagecount = $pdf->setSourceFile($infile); + for ($i = 1; $i <= $pagecount; $i++) { + $tplIdx = $pdf->importPage($i); + if ($tplIdx !== false) { + $s = $pdf->getTemplatesize($tplIdx); + $pdf->AddPage($s['h'] > $s['w'] ? 'P' : 'L'); + $pdf->useTemplate($tplIdx); + } else { + setEventMessages(null, array($infile.' cannot be added, probably protected PDF'), 'warnings'); + } + } + } + } + } + } + } + } + } + + $pdf->Close(); + + $pdf->Output($file, 'F'); + + //Add pdfgeneration hook + $hookmanager->initHooks(array('pdfgeneration')); + $parameters = array('file' => $file, 'object' => $object, 'outputlangs' => $outputlangs); + global $action; + $reshook = $hookmanager->executeHooks('afterPDFCreation', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks + if ($reshook < 0) { + $this->error = $hookmanager->error; + $this->errors = $hookmanager->errors; + } + + dolChmod($file); + + $this->result = array('fullpath' => $file); + + return 1; // No error + } else { + $this->error = $langs->trans("ErrorCanNotCreateDir", $dir); + return 0; + } + } else { + $this->error = $langs->trans("ErrorConstantNotDefined", "PROP_OUTPUTDIR"); + return 0; + } + } + + /** + * Show miscellaneous information (payment mode, payment term, ...) + * + * @param TCPDF $pdf Object PDF + * @param Propal $object Object to show + * @param int $posy Y + * @param Translate $outputlangs Langs object + * @return int Pos y + */ + public function drawInfoTable(&$pdf, $object, $posy, $outputlangs) + { + global $conf, $mysoc; + $default_font_size = pdf_getPDFFontSize($outputlangs); + + $pdf->SetFont('', '', $default_font_size - 1); + + $diffsizetitle = (!getDolGlobalString('PDF_DIFFSIZE_TITLE') ? 3 : $conf->global->PDF_DIFFSIZE_TITLE); + + // If France, show VAT mention if not applicable + if ($this->emetteur->country_code == 'FR' && empty($mysoc->tva_assuj)) { + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->MultiCell(100, 3, $outputlangs->transnoentities("VATIsNotUsedForInvoice"), 0, 'L', 0); + + $posy = $pdf->GetY() + 4; + } + + $posxval = 52; + if (getDolGlobalString('MAIN_PDF_DELIVERY_DATE_TEXT')) { + $displaydate = "daytext"; + } else { + $displaydate = "day"; + } + + // Show shipping date + if (!empty($object->delivery_date)) { + $outputlangs->load("sendings"); + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->SetXY($this->marge_gauche, $posy); + $titre = $outputlangs->transnoentities("DateDeliveryPlanned").':'; + $pdf->MultiCell(80, 4, $titre, 0, 'L'); + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->SetXY($posxval, $posy); + $dlp = dol_print_date($object->delivery_date, $displaydate, false, $outputlangs, true); + $pdf->MultiCell(80, 4, $dlp, 0, 'L'); + + $posy = $pdf->GetY() + 1; + } elseif ($object->availability_code || $object->availability) { // Show availability conditions + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->SetXY($this->marge_gauche, $posy); + $titre = $outputlangs->transnoentities("AvailabilityPeriod").':'; + $pdf->MultiCell(80, 4, $titre, 0, 'L'); + $pdf->SetTextColor(0, 0, 0); + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->SetXY($posxval, $posy); + $lib_availability = ($outputlangs->transnoentities("AvailabilityType".$object->availability_code) != 'AvailabilityType'.$object->availability_code) ? $outputlangs->transnoentities("AvailabilityType".$object->availability_code) : $outputlangs->convToOutputCharset($object->availability); + $lib_availability = str_replace('\n', "\n", $lib_availability); + $pdf->MultiCell(80, 4, $lib_availability, 0, 'L'); + + $posy = $pdf->GetY() + 1; + } + + // Show delivery mode + if (!getDolGlobalString('PROPOSAL_PDF_HIDE_DELIVERYMODE') && $object->shipping_method_id > 0) { + $outputlangs->load("sendings"); + + $shipping_method_id = $object->shipping_method_id; + if (getDolGlobalString('SOCIETE_ASK_FOR_SHIPPING_METHOD') && !empty($this->emetteur->shipping_method_id)) { + $shipping_method_id = $this->emetteur->shipping_method_id; + } + $shipping_method_code = dol_getIdFromCode($this->db, (string) $shipping_method_id, 'c_shipment_mode', 'rowid', 'code'); + $shipping_method_label = dol_getIdFromCode($this->db, (string) $shipping_method_id, 'c_shipment_mode', 'rowid', 'libelle'); + + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->SetXY($this->marge_gauche, $posy); + $titre = $outputlangs->transnoentities("SendingMethod").':'; + $pdf->MultiCell(43, 4, $titre, 0, 'L'); + + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->SetXY($posxval, $posy); + $lib_condition_paiement = ($outputlangs->transnoentities("SendingMethod".strtoupper($shipping_method_code)) != "SendingMethod".strtoupper($shipping_method_code)) ? $outputlangs->trans("SendingMethod".strtoupper($shipping_method_code)) : $shipping_method_label; + $lib_condition_paiement = str_replace('\n', "\n", $lib_condition_paiement); + $pdf->MultiCell(67, 4, $lib_condition_paiement, 0, 'L'); + + $posy = $pdf->GetY() + 1; + } + + // Show payments conditions + if (!getDolGlobalString('PROPOSAL_PDF_HIDE_PAYMENTTERM') && $object->cond_reglement_code) { + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->SetXY($this->marge_gauche, $posy); + $titre = $outputlangs->transnoentities("PaymentConditions").':'; + $pdf->MultiCell(43, 4, $titre, 0, 'L'); + + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->SetXY($posxval, $posy); + $lib_condition_paiement = $outputlangs->transnoentities("PaymentCondition".$object->cond_reglement_code) != 'PaymentCondition'.$object->cond_reglement_code ? $outputlangs->transnoentities("PaymentCondition".$object->cond_reglement_code) : $outputlangs->convToOutputCharset($object->cond_reglement_doc ? $object->cond_reglement_doc : $object->cond_reglement_label); + $lib_condition_paiement = str_replace('\n', "\n", $lib_condition_paiement); + if ($object->deposit_percent > 0) { + $lib_condition_paiement = str_replace('__DEPOSIT_PERCENT__', $object->deposit_percent, $lib_condition_paiement); + } + $pdf->MultiCell(67, 4, $lib_condition_paiement, 0, 'L'); + + $posy = $pdf->GetY() + 3; + } + + if (!getDolGlobalString('PROPOSAL_PDF_HIDE_PAYMENTMODE')) { + // Show payment mode + if ($object->mode_reglement_code + && $object->mode_reglement_code != 'CHQ' + && $object->mode_reglement_code != 'VIR') { + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->SetXY($this->marge_gauche, $posy); + $titre = $outputlangs->transnoentities("PaymentMode").':'; + $pdf->MultiCell(80, 5, $titre, 0, 'L'); + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->SetXY($posxval, $posy); + $lib_mode_reg = $outputlangs->transnoentities("PaymentType".$object->mode_reglement_code) != 'PaymentType'.$object->mode_reglement_code ? $outputlangs->transnoentities("PaymentType".$object->mode_reglement_code) : $outputlangs->convToOutputCharset($object->mode_reglement); + $pdf->MultiCell(80, 5, $lib_mode_reg, 0, 'L'); + + $posy = $pdf->GetY() + 2; + } + + // Show payment mode CHQ + if (empty($object->mode_reglement_code) || $object->mode_reglement_code == 'CHQ') { + // Si mode reglement non force ou si force a CHQ + if (getDolGlobalInt('FACTURE_CHQ_NUMBER')) { + if (getDolGlobalInt('FACTURE_CHQ_NUMBER') > 0) { + $account = new Account($this->db); + $account->fetch(getDolGlobalInt('FACTURE_CHQ_NUMBER')); + + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->MultiCell(100, 3, $outputlangs->transnoentities('PaymentByChequeOrderedTo', $account->owner_name), 0, 'L', 0); + $posy = $pdf->GetY() + 1; + + if (!getDolGlobalString('MAIN_PDF_HIDE_CHQ_ADDRESS')) { + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->MultiCell(100, 3, $outputlangs->convToOutputCharset($account->owner_address), 0, 'L', 0); + $posy = $pdf->GetY() + 2; + } + } + if (getDolGlobalInt('FACTURE_CHQ_NUMBER') == -1) { + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->MultiCell(100, 3, $outputlangs->transnoentities('PaymentByChequeOrderedTo', $this->emetteur->name), 0, 'L', 0); + $posy = $pdf->GetY() + 1; + + if (!getDolGlobalString('MAIN_PDF_HIDE_CHQ_ADDRESS')) { + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->MultiCell(100, 3, $outputlangs->convToOutputCharset($this->emetteur->getFullAddress()), 0, 'L', 0); + $posy = $pdf->GetY() + 2; + } + } + } + } + + // If payment mode not forced or forced to VIR, show payment with BAN + if (empty($object->mode_reglement_code) || $object->mode_reglement_code == 'VIR') { + if ($object->fk_account > 0 || $object->fk_bank > 0 || getDolGlobalInt('FACTURE_RIB_NUMBER')) { + $bankid = ($object->fk_account <= 0 ? getDolGlobalInt('FACTURE_RIB_NUMBER') : $object->fk_account); + if ($object->fk_bank > 0) { + $bankid = $object->fk_bank; // For backward compatibility when object->fk_account is forced with object->fk_bank + } + $account = new Account($this->db); + $account->fetch($bankid); + + $curx = $this->marge_gauche; + $cury = $posy; + + $posy = pdf_bank($pdf, $outputlangs, $curx, $cury, $account, 0, $default_font_size); + + $posy += 2; + } + } + } + + return $posy; + } + + + /** + * Show total to pay + * + * @param TCPDF $pdf Object PDF + * @param Propal $object Object proposal + * @param int $deja_regle Amount already paid (in the currency of invoice) + * @param int $posy Position depart + * @param Translate $outputlangs Object langs + * @param Translate $outputlangsbis Object lang for output bis + * @return int Position pour suite + */ + protected function drawTotalTable(&$pdf, $object, $deja_regle, $posy, $outputlangs, $outputlangsbis = null) + { + global $conf, $mysoc, $hookmanager; + + $default_font_size = pdf_getPDFFontSize($outputlangs); + + if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE') && $outputlangs->defaultlang != getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')) { + $outputlangsbis = new Translate('', $conf); + $outputlangsbis->setDefaultLang(getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')); + $outputlangsbis->loadLangs(array("main", "dict", "companies", "bills", "products", "propal")); + $default_font_size--; + } + + $tab2_top = $posy; + $tab2_hl = 4; + $pdf->SetFont('', '', $default_font_size - 1); + + // Total table + $col1x = 120; + $col2x = 170; + if ($this->page_largeur < 210) { // To work with US executive format + $col2x -= 20; + } + $largcol2 = ($this->page_largeur - $this->marge_droite - $col2x); + + $useborder = 0; + $index = 0; + + // Get Total HT + $total_ht = (isModEnabled("multicurrency") && $object->multicurrency_tx != 1 ? $object->multicurrency_total_ht : $object->total_ht); + + // Total remise + $total_line_remise = 0; + foreach ($object->lines as $i => $line) { + $resdiscount = pdfGetLineTotalDiscountAmount($object, $i, $outputlangs, 2); + $total_line_remise += (is_numeric($resdiscount) ? $resdiscount : 0); + // Gestion remise sous forme de ligne négative + if ($line->total_ht < 0) { + $total_line_remise += -$line->total_ht; + } + } + if ($total_line_remise > 0) { + $pdf->SetFillColor(255, 255, 255); + $pdf->SetXY($col1x, $tab2_top + $tab2_hl); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("TotalDiscount").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("TotalDiscount") : ''), 0, 'L', 1); + $pdf->SetXY($col2x, $tab2_top + $tab2_hl); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_line_remise, 0, $outputlangs), 0, 'R', 1); + + $index++; + + // Show total NET before discount + $pdf->SetFillColor(255, 255, 255); + $pdf->SetXY($col1x, $tab2_top); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("TotalHTBeforeDiscount").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("TotalHTBeforeDiscount") : ''), 0, 'L', 1); + $pdf->SetXY($col2x, $tab2_top); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_line_remise + $total_ht, 0, $outputlangs), 0, 'R', 1); + + $index++; + } + + // Total HT + $pdf->SetFillColor(255, 255, 255); + $pdf->SetXY($col1x, $tab2_top+ $tab2_hl * $index); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("TotalHT").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("TotalHT") : ''), 0, 'L', 1); + + $total_ht = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? $object->multicurrency_total_ht : $object->total_ht); + $pdf->SetXY($col2x, $tab2_top+ $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_ht + (!empty($object->remise) ? $object->remise : 0), 0, $outputlangs), 0, 'R', 1); + + // Show VAT by rates and total + $pdf->SetFillColor(248, 248, 248); + + $total_ttc = (isModEnabled("multicurrency") && $object->multicurrency_tx != 1) ? $object->multicurrency_total_ttc : $object->total_ttc; + + $this->atleastoneratenotnull = 0; + if (!getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT')) { + $tvaisnull = (!empty($this->tva) && count($this->tva) == 1 && isset($this->tva['0.000']) && is_float($this->tva['0.000'])); + if (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT_IFNULL') && $tvaisnull) { + // Nothing to do + } else { + //Local tax 1 before VAT + //if (getDolGlobalString('FACTURE_LOCAL_TAX1_OPTION') && getDolGlobalString('FACTURE_LOCAL_TAX1_OPTION') == 'localtax1on') + //{ + foreach ($this->localtax1 as $localtax_type => $localtax_rate) { + if (in_array((string) $localtax_type, array('1', '3', '5'))) { + continue; + } + + foreach ($localtax_rate as $tvakey => $tvaval) { + if ($tvakey != 0) { // On affiche pas taux 0 + //$this->atleastoneratenotnull++; + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $tvacompl = ''; + if (preg_match('/\*/', $tvakey)) { + $tvakey = str_replace('*', '', $tvakey); + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + } + $totalvat = $outputlangs->transcountrynoentities("TotalLT1", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalLT1", $mysoc->country_code) : ''); + $totalvat .= ' '; + $totalvat .= vatrate((string) abs((float) $tvakey), 1).$tvacompl; + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); + + $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, 'R', 1); + } + } + } + //} + //Local tax 2 before VAT + //if (getDolGlobalString('FACTURE_LOCAL_TAX2_OPTION') && getDolGlobalString('FACTURE_LOCAL_TAX2_OPTION') == 'localtax2on') + //{ + foreach ($this->localtax2 as $localtax_type => $localtax_rate) { + if (in_array((string) $localtax_type, array('1', '3', '5'))) { + continue; + } + + foreach ($localtax_rate as $tvakey => $tvaval) { + if ($tvakey != 0) { // On affiche pas taux 0 + //$this->atleastoneratenotnull++; + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $tvacompl = ''; + if (preg_match('/\*/', $tvakey)) { + $tvakey = str_replace('*', '', $tvakey); + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + } + $totalvat = $outputlangs->transcountrynoentities("TotalLT2", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalLT2", $mysoc->country_code) : ''); + $totalvat .= ' '; + $totalvat .= vatrate((string) abs((float) $tvakey), 1).$tvacompl; + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); + + $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, 'R', 1); + } + } + } + //} + + // VAT + foreach ($this->tva as $tvakey => $tvaval) { + if ($tvakey != 0) { // On affiche pas taux 0 + $this->atleastoneratenotnull++; + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $tvacompl = ''; + if (preg_match('/\*/', $tvakey)) { + $tvakey = str_replace('*', '', $tvakey); + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + } + $totalvat = $outputlangs->transcountrynoentities("TotalVAT", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalVAT", $mysoc->country_code) : ''); + $totalvat .= ' '; + $totalvat .= vatrate($tvakey, 1).$tvacompl; + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($tvaval, 0, $outputlangs), 0, 'R', 1); + } + } + + //Local tax 1 after VAT + //if (getDolGlobalString('FACTURE_LOCAL_TAX1_OPTION') && getDolGlobalString('FACTURE_LOCAL_TAX1_OPTION') == 'localtax1on') + //{ + foreach ($this->localtax1 as $localtax_type => $localtax_rate) { + if (in_array((string) $localtax_type, array('2', '4', '6'))) { + continue; + } + + foreach ($localtax_rate as $tvakey => $tvaval) { + if ($tvakey != 0) { // On affiche pas taux 0 + //$this->atleastoneratenotnull++; + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $tvacompl = ''; + if (preg_match('/\*/', $tvakey)) { + $tvakey = str_replace('*', '', $tvakey); + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + } + $totalvat = $outputlangs->transcountrynoentities("TotalLT1", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalLT1", $mysoc->country_code) : ''); + $totalvat .= ' '; + + $totalvat .= vatrate((string) abs((float) $tvakey), 1).$tvacompl; + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); + + $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, 'R', 1); + } + } + } + //} + //Local tax 2 after VAT + //if (getDolGlobalString('FACTURE_LOCAL_TAX2_OPTION') && getDolGlobalString('FACTURE_LOCAL_TAX2_OPTION') == 'localtax2on') + //{ + foreach ($this->localtax2 as $localtax_type => $localtax_rate) { + if (in_array((string) $localtax_type, array('2', '4', '6'))) { + continue; + } + + foreach ($localtax_rate as $tvakey => $tvaval) { + // retrieve global local tax + if ($tvakey != 0) { // On affiche pas taux 0 + //$this->atleastoneratenotnull++; + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $tvacompl = ''; + if (preg_match('/\*/', $tvakey)) { + $tvakey = str_replace('*', '', $tvakey); + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + } + $totalvat = $outputlangs->transcountrynoentities("TotalLT2", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalLT2", $mysoc->country_code) : ''); + $totalvat .= ' '; + + if (getDolGlobalString('PDF_LOCALTAX2_LABEL_IS_CODE_OR_RATE') == 'nocodenorate') { + $totalvat .= $tvacompl; + } else { + $totalvat .= vatrate((string) abs((float) $tvakey), 1).$tvacompl; + } + + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); + + $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, 'R', 1); + } + } + } + //} + + // Total TTC + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->SetTextColor(0, 0, 60); + $pdf->SetFillColor(224, 224, 224); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("TotalTTC").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("TotalTTC") : ''), $useborder, 'L', 1); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_ttc, 0, $outputlangs), $useborder, 'R', 1); + + // Deposit (acompte) line - MP Protection specific + $depositRate = getDolGlobalInt('PROPALE_MPPROTECTION_DEPOSIT_RATE', 40); + if ($depositRate > 0 && $depositRate <= 100) { + $depositAmount = price2num($total_ttc * $depositRate / 100, 'MT'); + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->SetTextColor(0, 0, 60); + $pdf->SetFillColor(255, 255, 255); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("Acompte").' ('.$depositRate.'%)', 0, 'L', 0); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($depositAmount, 0, $outputlangs), 0, 'R', 0); + } + } + } + + $pdf->SetTextColor(0, 0, 0); + + $resteapayer = 0; + /* + $resteapayer = $object->total_ttc - $deja_regle; + if (!empty($object->paye)) $resteapayer=0; + */ + + if ($deja_regle > 0) { + // Already paid + Deposits + $index++; + + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("AlreadyPaid").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("AlreadyPaid") : ''), 0, 'L', 0); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($deja_regle, 0, $outputlangs), 0, 'R', 0); + + /* + if ($object->close_code == 'discount_vat') { + $index++; + $pdf->SetFillColor(255,255,255); + + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("EscompteOfferedShort"), $useborder, 'L', 1); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($object->total_ttc - $deja_regle, 0, $outputlangs), $useborder, 'R', 1); + + $resteapayer=0; + } + */ + + $index++; + $pdf->SetTextColor(0, 0, 60); + $pdf->SetFillColor(224, 224, 224); + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("RemainderToPay").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities("RemainderToPay") : ''), $useborder, 'L', 1); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($resteapayer, 0, $outputlangs), $useborder, 'R', 1); + + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->SetTextColor(0, 0, 0); + } + + $parameters = array('pdf' => &$pdf, 'object' => &$object, 'outputlangs' => $outputlangs, 'index' => &$index, 'posy' => $posy); + + $reshook = $hookmanager->executeHooks('afterPDFTotalTable', $parameters, $this); // Note that $action and $object may have been modified by some hooks + if ($reshook < 0) { + $this->error = $hookmanager->error; + $this->errors = $hookmanager->errors; + } + + $index++; + return ($tab2_top + ($tab2_hl * $index)); + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Show table for lines + * + * @param TCPDF $pdf Object PDF + * @param float|int $tab_top Top position of table + * @param float|int $tab_height Height of table (rectangle) + * @param int $nexY Y (not used) + * @param Translate $outputlangs Langs object + * @param int $hidetop 1=Hide top bar of array and title, 0=Hide nothing, -1=Hide only title + * @param int $hidebottom Hide bottom bar of array + * @param string $currency Currency code + * @param Translate $outputlangsbis Langs object bis + * @return void + */ + protected function _tableau(&$pdf, $tab_top, $tab_height, $nexY, $outputlangs, $hidetop = 0, $hidebottom = 0, $currency = '', $outputlangsbis = null) + { + global $conf; + + // Force to disable hidetop and hidebottom + $hidebottom = 0; + if ($hidetop) { + $hidetop = -1; + } + + $currency = !empty($currency) ? $currency : $conf->currency; + $default_font_size = pdf_getPDFFontSize($outputlangs); + + // Amount in (at tab_top - 1) + $pdf->SetTextColor(0, 0, 0); + $pdf->SetFont('', '', $default_font_size - 2); + + if (empty($hidetop)) { + $titre = $outputlangs->transnoentities("AmountInCurrency", $outputlangs->transnoentitiesnoconv("Currency".$currency)); + if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE') && is_object($outputlangsbis)) { + $titre .= ' - '.$outputlangsbis->transnoentities("AmountInCurrency", $outputlangsbis->transnoentitiesnoconv("Currency".$currency)); + } + + $pdf->SetXY($this->page_largeur - $this->marge_droite - ($pdf->GetStringWidth($titre) + 3), $tab_top - 4); + $pdf->MultiCell(($pdf->GetStringWidth($titre) + 3), 2, $titre); + + //$conf->global->MAIN_PDF_TITLE_BACKGROUND_COLOR='230,230,230'; + if (getDolGlobalString('MAIN_PDF_TITLE_BACKGROUND_COLOR')) { + $pdf->RoundedRect($this->marge_gauche, $tab_top, $this->page_largeur - $this->marge_droite - $this->marge_gauche, $this->tabTitleHeight, $this->corner_radius, '1001', 'F', null, explode(',', getDolGlobalString('MAIN_PDF_TITLE_BACKGROUND_COLOR'))); + } + } + + $pdf->SetDrawColor(128, 128, 128); + $pdf->SetFont('', '', $default_font_size - 1); + + // Output Rect + $this->printRoundedRect($pdf, $this->marge_gauche, $tab_top, $this->page_largeur - $this->marge_gauche - $this->marge_droite, $tab_height, $this->corner_radius, $hidetop, $hidebottom, 'D'); // Rect takes a length in 3rd parameter and 4th parameter + + if (getDolGlobalString('MAIN_PDF_TITLE_TEXT_COLOR')) { + $arrayColorTextTitle = explode(',', getDolGlobalString('MAIN_PDF_TITLE_TEXT_COLOR')); + $pdf->SetTextColor($arrayColorTextTitle[0], $arrayColorTextTitle[1], $arrayColorTextTitle[2]); + } + + $this->pdfTabTitles($pdf, $tab_top, $tab_height, $outputlangs, $hidetop); + + if (getDolGlobalString('MAIN_PDF_TITLE_TEXT_COLOR')) { + $pdf->SetTextColor(0, 0, 0); + } + + if (empty($hidetop)) { + $pdf->line($this->marge_gauche, $tab_top + $this->tabTitleHeight, $this->page_largeur - $this->marge_droite, $tab_top + $this->tabTitleHeight); // line takes a position y in 2nd parameter and 4th parameter + } + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Show top header of page. + * + * @param TCPDF $pdf Object PDF + * @param Propal $object Object to show + * @param int $showaddress 0=no, 1=yes + * @param Translate $outputlangs Object lang for output + * @param Translate $outputlangsbis Object lang for output bis + * @return float|int Return topshift value + */ + protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $outputlangsbis = null) + { + global $conf, $langs; + + $ltrdirection = 'L'; + if ($outputlangs->trans("DIRECTION") == 'rtl') { + $ltrdirection = 'R'; + } + + // Load traductions files required by page + $outputlangs->loadLangs(array("main", "propal", "companies", "bills")); + + $default_font_size = pdf_getPDFFontSize($outputlangs); + + pdf_pagehead($pdf, $outputlangs, $this->page_hauteur); + + $pdf->SetTextColor(0, 0, 60); + $pdf->SetFont('', 'B', $default_font_size + 3); + + $w = 100; + + $posy = $this->marge_haute; + $posx = $this->page_largeur - $this->marge_droite - $w; + + $pdf->SetXY($this->marge_gauche, $posy); + + // Logo + if (!getDolGlobalInt('PDF_DISABLE_MYCOMPANY_LOGO')) { + if ($this->emetteur->logo) { + $logodir = $conf->mycompany->dir_output; + if (!empty($conf->mycompany->multidir_output[$object->entity])) { + $logodir = $conf->mycompany->multidir_output[$object->entity]; + } + if (!getDolGlobalInt('MAIN_PDF_USE_LARGE_LOGO')) { + $logo = $logodir.'/logos/thumbs/'.$this->emetteur->logo_small; + } else { + $logo = $logodir.'/logos/'.$this->emetteur->logo; + } + if (is_readable($logo)) { + $height = pdf_getHeightForLogo($logo); + $pdf->Image($logo, $this->marge_gauche, $posy, 0, $height); // width=0 (auto) + } else { + $pdf->SetTextColor(200, 0, 0); + $pdf->SetFont('', 'B', $default_font_size - 2); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ErrorLogoFileNotFound", $logo), 0, 'L'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ErrorGoToGlobalSetup"), 0, 'L'); + } + } else { + $text = $this->emetteur->name; + $pdf->MultiCell($w, 4, $outputlangs->convToOutputCharset($text), 0, $ltrdirection); + } + } + + $pdf->SetFont('', 'B', $default_font_size + 3); + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $title = $outputlangs->transnoentities("PdfCommercialProposalTitle"); + $title .= ' '.$outputlangs->convToOutputCharset($object->ref); + if ($object->status == $object::STATUS_DRAFT) { + $pdf->SetTextColor(128, 0, 0); + $title .= ' - '.$outputlangs->transnoentities("NotValidated"); + } + + $pdf->MultiCell($w, 4, $title, '', 'R'); + + $pdf->SetFont('', 'B', $default_font_size); + + /* + $posy += 5; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $textref = $outputlangs->transnoentities("Ref")." : ".$outputlangs->convToOutputCharset($object->ref); + if ($object->status == $object::STATUS_DRAFT) { + $pdf->SetTextColor(128, 0, 0); + $textref .= ' - '.$outputlangs->transnoentities("NotValidated"); + } + $pdf->MultiCell($w, 4, $textref, '', 'R'); + */ + + $posy += 3; + $pdf->SetFont('', '', $default_font_size - 2); + + $ref_customer = $object->ref_customer ?: $object->ref_client; + if ($ref_customer) { + $posy += 4; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefCustomer")." : ".dol_trunc($outputlangs->convToOutputCharset($ref_customer), 65), '', 'R'); + } + + if (getDolGlobalString('PDF_SHOW_PROJECT_TITLE')) { + $object->fetchProject(); + if (!empty($object->project->ref)) { + $posy += 3; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("Project")." : ".(empty($object->project->title) ? '' : $object->project->title), '', 'R'); + } + } + + if (getDolGlobalString('PDF_SHOW_PROJECT')) { + $object->fetchProject(); + if (!empty($object->project->ref)) { + $outputlangs->load("projects"); + $posy += 3; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefProject")." : ".(empty($object->project->ref) ? '' : $object->project->ref), '', 'R'); + } + } + + if (getDolGlobalString('MAIN_PDF_DATE_TEXT')) { + $displaydate = "daytext"; + } else { + $displaydate = "day"; + } + + //$posy += 4; + $posy = $pdf->getY(); + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("Date")." : ".dol_print_date($object->date, $displaydate, false, $outputlangs, true), '', 'R'); + + $posy += 4; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + + $title = $outputlangs->transnoentities("DateEndPropal"); + if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE') && is_object($outputlangsbis)) { + $title .= ' - '.$outputlangsbis->transnoentities("DateEndPropal"); + } + $pdf->MultiCell($w, 3, $title." : ".dol_print_date($object->fin_validite, $displaydate, false, $outputlangs, true), '', 'R'); + + if (!getDolGlobalString('MAIN_PDF_HIDE_CUSTOMER_CODE') && $object->thirdparty->code_client) { + $posy += 4; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("CustomerCode")." : ".$outputlangs->transnoentities($object->thirdparty->code_client), '', 'R'); + } + + if (!getDolGlobalString('MAIN_PDF_HIDE_CUSTOMER_ACCOUNTING_CODE') && $object->thirdparty->code_compta_client) { + $posy += 4; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("CustomerAccountancyCode")." : ".$outputlangs->transnoentities($object->thirdparty->code_compta_client), '', 'R'); + } + + // Get contact + if (getDolGlobalString('DOC_SHOW_FIRST_SALES_REP')) { + $arrayidcontact = $object->getIdContact('internal', 'SALESREPFOLL'); + if (count($arrayidcontact) > 0) { + $usertmp = new User($this->db); + $usertmp->fetch($arrayidcontact[0]); + $posy += 4; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("SalesRepresentative")." : ".$usertmp->getFullName($langs), '', 'R'); + } + } + + $posy += 2; + + $top_shift = 0; + // Show list of linked objects + $current_y = $pdf->getY(); + $posy = pdf_writeLinkedObjects($pdf, $object, $outputlangs, $posx, $posy, $w, 3, 'R', $default_font_size); + if ($current_y < $pdf->getY()) { + $top_shift = $pdf->getY() - $current_y; + } + + if ($showaddress) { + // Sender properties + $carac_emetteur = ''; + // Add internal contact of object if defined + $arrayidcontact = $object->getIdContact('internal', 'SALESREPFOLL'); + if (count($arrayidcontact) > 0) { + $object->fetch_user($arrayidcontact[0]); + $labelbeforecontactname = ($outputlangs->transnoentities("FromContactName") != 'FromContactName' ? $outputlangs->transnoentities("FromContactName") : $outputlangs->transnoentities("Name")); + $carac_emetteur .= ($carac_emetteur ? "\n" : '').$labelbeforecontactname." ".$outputlangs->convToOutputCharset($object->user->getFullName($outputlangs)); + $carac_emetteur .= (getDolGlobalInt('PDF_SHOW_PHONE_AFTER_USER_CONTACT') || getDolGlobalInt('PDF_SHOW_EMAIL_AFTER_USER_CONTACT')) ? ' (' : ''; + $carac_emetteur .= (getDolGlobalInt('PDF_SHOW_PHONE_AFTER_USER_CONTACT') && !empty($object->user->office_phone)) ? $object->user->office_phone : ''; + $carac_emetteur .= (getDolGlobalInt('PDF_SHOW_PHONE_AFTER_USER_CONTACT') && getDolGlobalInt('PDF_SHOW_EMAIL_AFTER_USER_CONTACT')) ? ', ' : ''; + $carac_emetteur .= (getDolGlobalInt('PDF_SHOW_EMAIL_AFTER_USER_CONTACT') && !empty($object->user->email)) ? $object->user->email : ''; + $carac_emetteur .= (getDolGlobalInt('PDF_SHOW_PHONE_AFTER_USER_CONTACT') || getDolGlobalInt('PDF_SHOW_EMAIL_AFTER_USER_CONTACT')) ? ')' : ''; + $carac_emetteur .= "\n"; + } + + $carac_emetteur .= pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, '', 0, 'source', $object); + + // Show sender + $posy = getDolGlobalString('MAIN_PDF_USE_ISO_LOCATION') ? 40 : 42; + $posy += $top_shift; + $posx = $this->marge_gauche; + if (getDolGlobalString('MAIN_INVERT_SENDER_RECIPIENT')) { + $posx = $this->page_largeur - $this->marge_droite - 80; + } + + $hautcadre = getDolGlobalString('MAIN_PDF_USE_ISO_LOCATION') ? 38 : 40; + $widthrecbox = getDolGlobalString('MAIN_PDF_USE_ISO_LOCATION') ? 92 : 82; + + // Show sender frame + if (!getDolGlobalString('MAIN_PDF_NO_SENDER_FRAME')) { + $pdf->SetTextColor(0, 0, 0); + $pdf->SetFont('', '', $default_font_size - 2); + $pdf->SetXY($posx, $posy - 5); + $pdf->MultiCell($widthrecbox, 5, $outputlangs->transnoentities("BillFrom"), 0, $ltrdirection); + $pdf->SetXY($posx, $posy); + $pdf->SetFillColor(230, 230, 230); + $pdf->RoundedRect($posx, $posy, $widthrecbox, $hautcadre, $this->corner_radius, '1234', 'F'); + $pdf->SetTextColor(0, 0, 60); + } + + // Show sender name + if (!getDolGlobalString('MAIN_PDF_HIDE_SENDER_NAME')) { + $pdf->SetXY($posx + 2, $posy + 3); + $pdf->SetFont('', 'B', $default_font_size); + $pdf->MultiCell($widthrecbox - 2, 4, $outputlangs->convToOutputCharset($this->emetteur->name), 0, $ltrdirection); + $posy = $pdf->getY(); + } + + // Show sender information + $pdf->SetXY($posx + 2, $posy); + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->MultiCell($widthrecbox - 2, 4, $carac_emetteur, 0, $ltrdirection); + + + // If CUSTOMER contact defined, we use it + $usecontact = false; + $arrayidcontact = $object->getIdContact('external', 'CUSTOMER'); + if (count($arrayidcontact) > 0) { + $usecontact = true; + $result = $object->fetch_contact($arrayidcontact[0]); + } + + // Recipient name + if ($usecontact && ($object->contact->socid != $object->thirdparty->id && (!isset($conf->global->MAIN_USE_COMPANY_NAME_OF_CONTACT) || getDolGlobalString('MAIN_USE_COMPANY_NAME_OF_CONTACT')))) { + $thirdparty = $object->contact; + } else { + $thirdparty = $object->thirdparty; + } + + $carac_client_name = pdfBuildThirdpartyName($thirdparty, $outputlangs); + + $mode = 'target'; + $carac_client = pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, ($usecontact ? $object->contact : ''), ($usecontact ? 1 : 0), $mode, $object); + + // Show recipient + $widthrecbox = getDolGlobalString('MAIN_PDF_USE_ISO_LOCATION') ? 92 : 100; + if ($this->page_largeur < 210) { + $widthrecbox = 84; // To work with US executive format + } + $posy = getDolGlobalString('MAIN_PDF_USE_ISO_LOCATION') ? 40 : 42; + $posy += $top_shift; + $posx = $this->page_largeur - $this->marge_droite - $widthrecbox; + if (getDolGlobalString('MAIN_INVERT_SENDER_RECIPIENT')) { + $posx = $this->marge_gauche; + } + + // Show recipient frame + if (!getDolGlobalString('MAIN_PDF_NO_RECIPENT_FRAME')) { + $pdf->SetTextColor(0, 0, 0); + $pdf->SetFont('', '', $default_font_size - 2); + $pdf->SetXY($posx + 2, $posy - 5); + $pdf->MultiCell($widthrecbox, 5, $outputlangs->transnoentities("BillTo"), 0, $ltrdirection); + $pdf->RoundedRect($posx, $posy, $widthrecbox, $hautcadre, $this->corner_radius, '1234', 'D'); + } + + // Show recipient name + $pdf->SetXY($posx + 2, $posy + 3); + $pdf->SetFont('', 'B', $default_font_size); + // @phan-suppress-next-line PhanPluginSuspiciousParamOrder + $pdf->MultiCell($widthrecbox, 2, $carac_client_name, 0, $ltrdirection); + + $posy = $pdf->getY(); + + // Show recipient information + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->SetXY($posx + 2, $posy); + // @phan-suppress-next-line PhanPluginSuspiciousParamOrder + $pdf->MultiCell($widthrecbox, 4, $carac_client, 0, $ltrdirection); + } + + $pdf->SetTextColor(0, 0, 0); + + return $top_shift; + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Show footer of page. Need this->emetteur object + * + * @param TCPDF $pdf PDF + * @param Propal $object Object to show + * @param Translate $outputlangs Object lang for output + * @param int $hidefreetext 1=Hide free text + * @return int Return height of bottom margin including footer text + */ + protected function _pagefoot(&$pdf, $object, $outputlangs, $hidefreetext = 0) + { + $showdetails = getDolGlobalInt('MAIN_GENERATE_DOCUMENTS_SHOW_FOOT_DETAILS', 0); + return pdf_pagefoot($pdf, $outputlangs, 'PROPOSAL_FREE_TEXT', $this->emetteur, $this->marge_basse, $this->marge_gauche, $this->page_hauteur, $object, $showdetails, $hidefreetext, $this->page_largeur, $this->watermark); + } + + /** + * Show area for the customer to sign + * + * @param TCPDF $pdf Object PDF + * @param Propal $object Object proposal + * @param int $posy Position depart + * @param Translate $outputlangs Object langs + * @return int Position pour suite + */ + protected function drawSignatureArea(&$pdf, $object, $posy, $outputlangs) + { + $default_font_size = pdf_getPDFFontSize($outputlangs); + $tab_top = $posy + 4; + $tab_hl = 4; + + $posx = 120; + $largcol = ($this->page_largeur - $this->marge_droite - $posx); + + // Total HT + $pdf->SetFillColor(255, 255, 255); + $pdf->SetXY($posx, $tab_top); + $pdf->SetFont('', '', $default_font_size - 2); + $pdf->MultiCell($largcol, $tab_hl, $outputlangs->transnoentities("ProposalCustomerSignature"), 0, 'L', 1); + + $pdf->SetXY($posx, $tab_top + $tab_hl + 3); + //$pdf->MultiCell($largcol, $tab_hl * 3, '', 1, 'R'); + $pdf->RoundedRect($posx, $tab_top + $tab_hl + 3, $largcol, $tab_hl * 3, $this->corner_radius, '1234', 'D'); + + + if (getDolGlobalString('MAIN_PDF_PROPAL_USE_ELECTRONIC_SIGNING')) { + // Can be retrieve with getSignatureAppearanceArray() + // Can be also detected by putting the mouse over the area when using evince pdf reader + $pdf->addEmptySignatureAppearance($posx, $tab_top + $tab_hl, $largcol, $tab_hl * 3); + } + + return ($tab_hl * 7); + } + + + /** + * Define Array Column Field + * + * @param Propal $object object proposal + * @param Translate $outputlangs langs + * @param int $hidedetails Do not show line details + * @param int $hidedesc Do not show desc + * @param int $hideref Do not show ref + * @return void + */ + public function defineColumnField($object, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0) + { + global $hookmanager; + + // Default field style for content + $this->defaultContentsFieldsStyle = array( + 'align' => 'R', // R,C,L + 'padding' => array(1, 0.5, 1, 0.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ); + + // Default field style for content + $this->defaultTitlesFieldsStyle = array( + 'align' => 'C', // R,C,L + 'padding' => array(0.5, 0, 0.5, 0), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ); + + /* + * For example + $this->cols['theColKey'] = array( + 'rank' => $rank, // int : use for ordering columns + 'width' => 20, // the column width in mm + 'title' => array( + 'textkey' => 'yourLangKey', // if there is no label, yourLangKey will be translated to replace label + 'label' => ' ', // the final label : used fore final generated text + 'align' => 'L', // text alignment : R,C,L + 'padding' => array(0.5,0.5,0.5,0.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ), + 'content' => array( + 'align' => 'L', // text alignment : R,C,L + 'padding' => array(0.5,0.5,0.5,0.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ), + ); + */ + + $rank = 0; // do not use negative rank + $this->cols['position'] = array( + 'rank' => $rank, + 'width' => 10, + 'status' => getDolGlobalInt('PDF_CYAN_ADD_POSITION') ? true : (getDolGlobalInt('PDF_ADD_POSITION') ? true : false), + 'title' => array( + 'textkey' => '#', // use lang key is useful in somme case with module + 'align' => 'C', + // 'textkey' => 'yourLangKey', // if there is no label, yourLangKey will be translated to replace label + // 'label' => ' ', // the final label + 'padding' => array(0.5, 0.5, 0.5, 0.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ), + 'content' => array( + 'align' => 'C', + 'padding' => array(1, 0.5, 1, 1.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ), + ); + + $rank = 5; // do not use negative rank + $this->cols['desc'] = array( + 'rank' => $rank, + 'width' => false, // only for desc + 'status' => true, + 'title' => array( + 'textkey' => 'Designation', // use lang key is useful in somme case with module + 'align' => 'L', + // 'textkey' => 'yourLangKey', // if there is no label, yourLangKey will be translated to replace label + // 'label' => ' ', // the final label + 'padding' => array(0.5, 0.5, 0.5, 0.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ), + 'content' => array( + 'align' => 'L', + 'padding' => array(1, 0.5, 1, 1.5), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ), + ); + + // Image of product + $rank += 10; + $this->cols['photo'] = array( + 'rank' => $rank, + 'width' => getDolGlobalInt('MAIN_DOCUMENTS_WITH_PICTURE_WIDTH', 20), // in mm + 'status' => false, + 'title' => array( + 'textkey' => 'Photo', + 'label' => ' ' + ), + 'content' => array( + 'padding' => array(0, 0, 0, 0), // Like css 0 => top , 1 => right, 2 => bottom, 3 => left + ), + 'border-left' => false, // remove left line separator + ); + + if (getDolGlobalString('MAIN_GENERATE_PROPOSALS_WITH_PICTURE') && !empty($this->atleastonephoto)) { + $this->cols['photo']['status'] = true; + $this->cols['photo']['border-left'] = true; + } + + + $rank += 10; + $this->cols['vat'] = array( + 'rank' => $rank, + 'status' => false, + 'width' => 16, // in mm + 'title' => array( + 'textkey' => 'VAT' + ), + 'border-left' => true, // add left line separator + ); + + if (!getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT') && !getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT_COLUMN')) { + $this->cols['vat']['status'] = true; + } + + $rank += 10; + $this->cols['subprice'] = array( + 'rank' => $rank, + 'width' => 19, // in mm + 'status' => true, + 'title' => array( + 'textkey' => 'PriceUHT' + ), + 'border-left' => true, // add left line separator + ); + + // Adapt dynamically the width of subprice, if text is too long. + $tmpwidth = 0; + $nblines = count($object->lines); + for ($i = 0; $i < $nblines; $i++) { + $tmpwidth2 = dol_strlen(dol_string_nohtmltag(pdf_getlineupexcltax($object, $i, $outputlangs, $hidedetails))); + $tmpwidth = max($tmpwidth, $tmpwidth2); + } + if ($tmpwidth > 10) { + $this->cols['subprice']['width'] += (2 * ($tmpwidth - 10)); + } + + $rank += 10; + $this->cols['qty'] = array( + 'rank' => $rank, + 'width' => 16, // in mm + 'status' => true, + 'title' => array( + 'textkey' => 'Qty' + ), + 'border-left' => true, // add left line separator + ); + + $rank += 10; + $this->cols['unit'] = array( + 'rank' => $rank, + 'width' => 11, // in mm + 'status' => false, + 'title' => array( + 'textkey' => 'Unit' + ), + 'border-left' => true, // add left line separator + ); + if (getDolGlobalInt('PRODUCT_USE_UNITS')) { + $this->cols['unit']['status'] = true; + } + + $rank += 10; + $this->cols['discount'] = array( + 'rank' => $rank, + 'width' => 13, // in mm + 'status' => false, + 'title' => array( + 'textkey' => 'ReductionShort' + ), + 'border-left' => true, // add left line separator + ); + if ($this->atleastonediscount) { + $this->cols['discount']['status'] = true; + } + + $rank += 1000; // add a big offset to be sure is the last col because default extrafield rank is 100 + $this->cols['totalexcltax'] = array( + 'rank' => $rank, + 'width' => 26, // in mm + 'status' => !getDolGlobalString('PDF_PROPAL_HIDE_PRICE_EXCL_TAX'), + 'title' => array( + 'textkey' => 'TotalHTShort' + ), + 'border-left' => true, // add left line separator + ); + + $rank += 1010; // add a big offset to be sure is the last col because default extrafield rank is 100 + $this->cols['totalincltax'] = array( + 'rank' => $rank, + 'width' => 26, // in mm + 'status' => getDolGlobalBool('PDF_PROPAL_SHOW_PRICE_INCL_TAX'), + 'title' => array( + 'textkey' => 'TotalTTCShort' + ), + 'border-left' => true, // add left line separator + ); + + // Add extrafields cols + if (!empty($object->lines)) { + $line = reset($object->lines); + $this->defineColumnExtrafield($line, $outputlangs, $hidedetails); + } + + $parameters = array( + 'object' => $object, + 'outputlangs' => $outputlangs, + 'hidedetails' => $hidedetails, + 'hidedesc' => $hidedesc, + 'hideref' => $hideref + ); + + $reshook = $hookmanager->executeHooks('defineColumnField', $parameters, $this); // Note that $object may have been modified by hook + if ($reshook < 0) { + setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); + } elseif (empty($reshook)) { + // @phan-suppress-next-line PhanPluginSuspiciousParamOrderInternal + $this->cols = array_replace($this->cols, $hookmanager->resArray); // array_replace is used to preserve keys + } else { + $this->cols = $hookmanager->resArray; + } + } +}