From 664e401166878221411c849cf73a7f3776dfd3f8 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Thu, 6 Aug 2026 17:51:14 +0200 Subject: [PATCH 01/15] fix: add missing translation file in mo_mouvement (#39406) --- htdocs/mrp/mo_movements.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/mrp/mo_movements.php b/htdocs/mrp/mo_movements.php index 5a23604bae8..b0f9c4dbc9b 100644 --- a/htdocs/mrp/mo_movements.php +++ b/htdocs/mrp/mo_movements.php @@ -48,7 +48,7 @@ require_once DOL_DOCUMENT_ROOT.'/mrp/lib/mrp_mo.lib.php'; */ // Load translation files required by the page -$langs->loadLangs(array("mrp", "stocks", "other")); +$langs->loadLangs(array("mrp", "stocks", "other", "productbatch")); // Get parameters $id = GETPOSTINT('id'); From 658c35a8af186c78dfca42c757d6217b28cc4556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20FRANCE?= Date: Thu, 6 Aug 2026 17:51:51 +0200 Subject: [PATCH 02/15] fix phpstan in v23 (#39399) * fix phpstan * Update agenda.php * Update agenda.php * Update agenda.php --- htdocs/contact/agenda.php | 2 +- htdocs/contrat/agenda.php | 2 +- htdocs/product/agenda.php | 2 +- htdocs/projet/agenda.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/htdocs/contact/agenda.php b/htdocs/contact/agenda.php index edcc64b8f94..35e64572768 100644 --- a/htdocs/contact/agenda.php +++ b/htdocs/contact/agenda.php @@ -324,7 +324,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { $param .= '&search_complete='.urlencode($search_complete); } if ($search_filtert != '') { - $param .= '&search_filtert='.urlencode($search_filtert); + $param .= '&search_filtert='.urlencode((string) $search_filtert); } if ($search_dateevent_start != '') { $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); diff --git a/htdocs/contrat/agenda.php b/htdocs/contrat/agenda.php index 3dbd73f1aee..b53c97649e1 100644 --- a/htdocs/contrat/agenda.php +++ b/htdocs/contrat/agenda.php @@ -307,7 +307,7 @@ if ($object->id > 0) { $param .= '&search_complete='.urlencode($search_complete); } if ($search_filtert != '') { - $param .= '&search_filtert='.urlencode($search_filtert); + $param .= '&search_filtert='.urlencode((string) $search_filtert); } if ($search_dateevent_start != '') { $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); diff --git a/htdocs/product/agenda.php b/htdocs/product/agenda.php index 3bfeda941b0..9b534ad266f 100644 --- a/htdocs/product/agenda.php +++ b/htdocs/product/agenda.php @@ -251,7 +251,7 @@ if (isModEnabled('agenda') && ($user->hasRight('agenda', 'myactions', 'read') || $param .= '&search_complete='.urlencode($search_complete); } if ($search_filtert != '') { - $param .= '&search_filtert='.urlencode($search_filtert); + $param .= '&search_filtert='.urlencode((string) $search_filtert); } if ($search_dateevent_start != '') { $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); diff --git a/htdocs/projet/agenda.php b/htdocs/projet/agenda.php index 9456df8ff7b..431459bd0d5 100644 --- a/htdocs/projet/agenda.php +++ b/htdocs/projet/agenda.php @@ -245,7 +245,7 @@ if (!empty($object->id)) { $param .= '&search_complete='.urlencode($search_complete); } if ($search_filtert != '') { - $param .= '&search_filtert='.urlencode($search_filtert); + $param .= '&search_filtert='.urlencode((string) $search_filtert); } if ($search_dateevent_start != '') { $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); From ea1443e09ed70cd9afd7f62ccf4f24796b4b3093 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Thu, 6 Aug 2026 17:52:42 +0200 Subject: [PATCH 03/15] fix: in MO split line broken on qty of component less than 1 (#39407) * fix: in MO split line broken on qty less than 1 and over consumption * fix: in MO split line broken on qty less than 1 --- htdocs/mrp/js/lib_dispatch.js.php | 6 +++--- htdocs/mrp/mo_production.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/htdocs/mrp/js/lib_dispatch.js.php b/htdocs/mrp/js/lib_dispatch.js.php index d63cc494c2b..b988d6be292 100644 --- a/htdocs/mrp/js/lib_dispatch.js.php +++ b/htdocs/mrp/js/lib_dispatch.js.php @@ -146,10 +146,10 @@ function addDispatchLine(index, type, mode) } if (error === 0) { - addDispatchTR(qtyOrdered, qtyDispatched, index, nbrTrs, warehouseId, inputId, type, '', mode, $row); + addDispatchTR(qtyOrdered, qtyDispatched, index, nbrTrs, warehouseId, inputId, type, '', mode, $row, allowoverconsumption); } } else { - addDispatchTR(qtyOrdered, qtyDispatched, index, nbrTrs, warehouseId, inputId, type, qty, mode, $row); + addDispatchTR(qtyOrdered, qtyDispatched, index, nbrTrs, warehouseId, inputId, type, qty, mode, $row, allowoverconsumption); } } @@ -170,7 +170,7 @@ function addDispatchLine(index, type, mode) * @param $row object */ function addDispatchTR(qtyOrdered, qtyDispatched, index, nbrTrs, warehouseId, inputId, type, qty, mode, $row) { - if (qtyOrdered <= 1) { + if (qtyOrdered <= 0) { let errormsg = 'trans('QtyCantBeSplit')); ?>'; $.jnotify(errormsg, 'error', true); return -1; diff --git a/htdocs/mrp/mo_production.php b/htdocs/mrp/mo_production.php index 5b5aecca496..6af93df71cf 100644 --- a/htdocs/mrp/mo_production.php +++ b/htdocs/mrp/mo_production.php @@ -1540,13 +1540,13 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea // Split $type = 'batch'; print ''; - print ' '.img_picto($langs->trans('AddStockLocationLine'), 'split', 'class="splitbutton" onClick="addDispatchLine('.((int) $line->id).', \''.dol_escape_js($type).'\', \'qtymissingconsume\')"'); + print ' '.img_picto($langs->trans('AddStockLocationLine'), 'split', 'class="splitbutton" onClick="addDispatchLine('.((int) $line->id).', \''.dol_escape_js($type).'\', \'qtymissingconsume\', 1)"'); print ''; // Split All print ''; if (($action == 'consumeorproduce' || $action == 'consumeandproduceall') && $tmpproduct->status_batch == 2) { - print img_picto($langs->trans('SplitAllQuantity'), 'split', 'class="splitbutton splitallbutton field-error-icon" data-max-qty="1" onClick="addDispatchLine('.$line->id.', \'batch\', \'allmissingconsume\')"'); + print img_picto($langs->trans('SplitAllQuantity'), 'split', 'class="splitbutton splitallbutton field-error-icon" data-max-qty="1" onClick="addDispatchLine('.$line->id.', \'batch\', \'allmissingconsume\', 1)"'); } print ''; From 322e41b81a1e94d23f664158b09c7df9d76d4e2a Mon Sep 17 00:00:00 2001 From: tcesar Date: Thu, 6 Aug 2026 17:58:33 +0200 Subject: [PATCH 04/15] Fix: API auth fails in multicompany transverse mode when user has several groups (#39409) --- htdocs/api/class/api_access.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/api/class/api_access.class.php b/htdocs/api/class/api_access.class.php index f1f3d8d9ef2..04727c337da 100644 --- a/htdocs/api/class/api_access.class.php +++ b/htdocs/api/class/api_access.class.php @@ -136,7 +136,7 @@ class DolibarrApiAccess implements iAuthenticate if (!getDolGlobalString('API_IN_TOKEN_TABLE')) { if (isModEnabled('multicompany') && getDolGlobalString('MULTICOMPANY_TRANSVERSE_MODE') && defined("DOLENTITY")) { - $sql = "SELECT u.login, u.datec, u.api_key as use_api, u.api_key as api_key, 0 as token_rowid,"; + $sql = "SELECT DISTINCT u.login, u.datec, u.api_key as use_api, u.api_key as api_key, 0 as token_rowid,"; $sql .= " u.tms as date_modification,"; $sql .= " gu.entity, gu.entity as token_entity"; $sql .= " FROM ".$this->db->prefix()."user as u"; @@ -150,7 +150,7 @@ class DolibarrApiAccess implements iAuthenticate } } else { if (isModEnabled('multicompany') && getDolGlobalString('MULTICOMPANY_TRANSVERSE_MODE') && defined("DOLENTITY")) { - $sql = "SELECT u.login, u.datec, u.api_key as use_api, oat.tokenstring as api_key, oat.entity as token_entity, rowid as token_rowid,"; + $sql = "SELECT DISTINCT u.login, u.datec, u.api_key as use_api, oat.tokenstring as api_key, oat.entity as token_entity, rowid as token_rowid,"; $sql .= " oat.tms as date_modification,"; $sql .= " gu.entity"; $sql .= " FROM ".$this->db->prefix()."oauth_token AS oat"; From 2b90d1f14fe23356259e8af783f449ad21aa698f Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 7 Aug 2026 15:18:38 +0200 Subject: [PATCH 05/15] Restore ability to add all lines under subtotal title --- .../modules/propale/doc/pdf_cyan.modules.php | 328 +++++++++--------- 1 file changed, 173 insertions(+), 155 deletions(-) diff --git a/htdocs/core/modules/propale/doc/pdf_cyan.modules.php b/htdocs/core/modules/propale/doc/pdf_cyan.modules.php index 7582333f63c..4fc395a4696 100644 --- a/htdocs/core/modules/propale/doc/pdf_cyan.modules.php +++ b/htdocs/core/modules/propale/doc/pdf_cyan.modules.php @@ -567,12 +567,15 @@ class pdf_cyan extends ModelePDFPropales $pdf_sub_options['titleshowuponpdf'] = 1; $pdf_sub_options['titleshowtotalexludingvatonpdf'] = 1; + $hidenextline = 0; + for ($i = 0; $i < $nblines; $i++) { $linePosition = $i + 1; $curY = $nexY; $sub_options = $object->lines[$i]->extraparams["subtotal"] ?? array(); + if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE) { $level = $object->lines[$i]->qty; if ($sub_options) { @@ -596,169 +599,179 @@ class pdf_cyan extends ModelePDFPropales } } - if (($curY + 6) > ($this->page_hauteur - $heightforfooter) || isset($sub_options['titleforcepagebreak']) && !($pdf->getNumPages() == 1 && $curY == $tab_top + $this->tabTitleHeight)) { - $object->lines[$i]->pagebreak = true; + if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE && isset($sub_options['subtotalshowtotalexludingvatonpdf']) && getDolGlobalString('SUBTOTAL_HIDE_LINES_UNDER_TITLE')) { // TODO Use $sub_options['titlehidelinesundertitle'] instead of SUBTOTAL_HIDE_LINES_UNDER_TITLE + $hidenextline = 0; + $pdf_sub_options = array(); + $pdf_sub_options['titleshowuponpdf'] = 1; + $pdf_sub_options['titleshowtotalexludingvatonpdf'] = 1; } - // 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); + if ($hidenextline) { + $linePosition--; + } else { + if (($curY + 6) > ($this->page_hauteur - $heightforfooter) || isset($sub_options['titleforcepagebreak']) && !($pdf->getNumPages() == 1 && $curY == $tab_top + $this->tabTitleHeight)) { + $object->lines[$i]->pagebreak = true; } - $pdf->setPage($pdf->getNumPages()); - $nexY = $curY = $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('', true, $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); + // 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($pageposbefore + 1); - $pdf->setPageOrientation('', true, $heightforfooter); // The only function to edit the bottom margin of current page to set it. - $curY = $tab_top_newpage; - $showpricebeforepagebreak = 0; + + $pdf->setPage($pdf->getNumPages()); + $nexY = $curY = $tab_top_newpage; } - $pdf->setPageOrientation('', false, $heightforfooter + $heightforfreetext); // The only function to edit the bottom margin of current page to set it. - // @phan-suppress-next-line PhanTypeMismatchProperty - 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->resetAfterColsLinePositionsData($nexY, $pdf->getPage()); - $this->setAfterColsLinePositionsData('photo', $posYAfterImage, $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]); } - } - // restore Page orientation for text - $pdf->setPageOrientation('', true, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + $pdf->setTopMargin($tab_top_newpage); + $pdf->setPageOrientation('', true, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + $pageposbefore = $pdf->getPage(); + $curYBefore = $curY; - if ($this->getColumnStatus('desc')) { - if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { - $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); - $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); - } else { - $bg_color = colorStringToArray(getDolGlobalString("SUBTOTAL_BACK_COLOR_LEVEL_".abs($object->lines[$i]->qty), 'ffffff')); - pdf_render_subtotals($pdf, $this, $curY, $object, $i, $outputlangs, $hideref, $hidedesc, $bg_color, true, true); + // 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('', true, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + $curY = $tab_top_newpage; + $showpricebeforepagebreak = 0; + } + + $pdf->setPageOrientation('', false, $heightforfooter + $heightforfreetext); // The only function to edit the bottom margin of current page to set it. + // @phan-suppress-next-line PhanTypeMismatchProperty + 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()); + } } - } - $afterPosData = $this->getMaxAfterColsLinePositionsData(); - $pdf->setPage($pageposbefore); - $pdf->setTopMargin($this->marge_haute); - $curY = $curYBefore; - $pdf->setPageOrientation('', false, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + // restore Page orientation for text + $pdf->setPageOrientation('', true, $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') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { - $vat_rate = pdf_getlinevatrate($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'vat', $vat_rate); - } - - // Unit price before discount - if ($this->getColumnStatus('subprice') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowuponpdf'])) { - $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') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { - $qty = pdf_getlineqty($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'qty', $qty); - } - - - // Unit - if ($this->getColumnStatus('unit') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { - $unit = pdf_getlineunit($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'unit', $unit); - } - - // Discount on line - if ($this->getColumnStatus('discount') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && $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')) { - if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowtotalexludingvatonpdf'])) { - $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); - } elseif ($object->lines[$i]->qty < 0 && isset($sub_options['subtotalshowtotalexludingvatonpdf'])) { - if (isModEnabled('multicurrency') && $object->multicurrency_code != $conf->currency) { - $total_excl_tax = $object->getSubtotalLineMulticurrencyAmount($object->lines[$i]); + if ($this->getColumnStatus('desc')) { + if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); + $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); } else { - $total_excl_tax = $object->getSubtotalLineAmount($object->lines[$i]); - } - $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()); + $bg_color = colorStringToArray(getDolGlobalString("SUBTOTAL_BACK_COLOR_LEVEL_".abs($object->lines[$i]->qty), 'ffffff')); + pdf_render_subtotals($pdf, $this, $curY, $object, $i, $outputlangs, $hideref, $hidedesc, $bg_color, true, true); } } - } - $afterPosData = $this->getMaxAfterColsLinePositionsData(); - $parameters = array( + $afterPosData = $this->getMaxAfterColsLinePositionsData(); + $pdf->setPage($pageposbefore); + $pdf->setTopMargin($this->marge_haute); + $curY = $curYBefore; + $pdf->setPageOrientation('', false, $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') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $vat_rate = pdf_getlinevatrate($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'vat', $vat_rate); + } + + // Unit price before discount + if ($this->getColumnStatus('subprice') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowuponpdf'])) { + $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') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $qty = pdf_getlineqty($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'qty', $qty); + } + + + // Unit + if ($this->getColumnStatus('unit') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $unit = pdf_getlineunit($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'unit', $unit); + } + + // Discount on line + if ($this->getColumnStatus('discount') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && $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')) { + if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowtotalexludingvatonpdf'])) { + $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); + } elseif ($object->lines[$i]->qty < 0 && isset($sub_options['subtotalshowtotalexludingvatonpdf'])) { + if (isModEnabled('multicurrency') && $object->multicurrency_code != $conf->currency) { + $total_excl_tax = $object->getSubtotalLineMulticurrencyAmount($object->lines[$i]); + } else { + $total_excl_tax = $object->getSubtotalLineAmount($object->lines[$i]); + } + $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, @@ -766,9 +779,9 @@ class pdf_cyan extends ModelePDFPropales '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 - + ); + $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) { @@ -837,17 +850,22 @@ class pdf_cyan extends ModelePDFPropales $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')] = array('vatrate' => $vatrate, 'vatcode' => $vatcode, 'amount' => $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'] + $tvaligne); + if (!$hidenextline) { + $afterPosData = $this->getMaxAfterColsLinePositionsData(); + $pdf->setPage($afterPosData['page']); + $nexY = $afterPosData['y']; - $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, $this->page_largeur - $this->marge_droite, $nexY); + $pdf->SetLineStyle(array('dash' => 0)); + } + } - // 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, $this->page_largeur - $this->marge_droite, $nexY); - $pdf->SetLineStyle(array('dash' => 0)); + if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE && isset($sub_options['titleshowuponpdf']) && getDolGlobalString('SUBTOTAL_HIDE_LINES_UNDER_TITLE')) { // TODO Use $sub_options['titlehidelinesundertitle'] instead of SUBTOTAL_HIDE_LINES_UNDER_TITLE + $hidenextline = 1; } $nexY += 0; // Add space between lines From a80eadb3564a879272219634de9ee523108d80ae Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 10 Aug 2026 11:40:49 +0200 Subject: [PATCH 06/15] Backport sanitization from v24 --- htdocs/commande/class/commande.class.php | 30 ++++++++++++------------ htdocs/societe/class/societe.class.php | 2 +- htdocs/user/class/user.class.php | 8 +++---- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/htdocs/commande/class/commande.class.php b/htdocs/commande/class/commande.class.php index 1640c361d0b..d9bc9665987 100644 --- a/htdocs/commande/class/commande.class.php +++ b/htdocs/commande/class/commande.class.php @@ -3419,26 +3419,26 @@ class Commande extends CommonOrder $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").","; $sql .= " date_commande=".(strval($this->date_commande) != '' ? "'".$this->db->idate($this->date_commande)."'" : 'null').","; $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').","; - $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").","; - $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").","; - $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").","; - $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").","; - $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").","; - $sql .= " fk_statut=".(isset($this->status) ? $this->status : "null").","; - $sql .= " fk_user_modif=".(isset($user->id) ? $user->id : "null").","; - $sql .= " fk_user_valid=".((isset($this->user_validation_id) && $this->user_validation_id > 0) ? $this->user_validation_id : "null").","; - $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").","; - $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").","; - $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? strval($this->deposit_percent) : "null").","; + $sql .= " total_tva=".(isset($this->total_tva) ? (float) $this->total_tva : "null").","; + $sql .= " localtax1=".(isset($this->total_localtax1) ? (float) $this->total_localtax1 : "null").","; + $sql .= " localtax2=".(isset($this->total_localtax2) ? (float) $this->total_localtax2 : "null").","; + $sql .= " total_ht=".(isset($this->total_ht) ? (float) $this->total_ht : "null").","; + $sql .= " total_ttc=".(isset($this->total_ttc) ? (float) $this->total_ttc : "null").","; + $sql .= " fk_statut=".(isset($this->status) ? (int) $this->status : "null").","; + $sql .= " fk_user_modif=".(isset($user->id) ? (int) $user->id : "null").","; + $sql .= " fk_user_valid=".((isset($this->user_validation_id) && $this->user_validation_id > 0) ? (int) $this->user_validation_id : "null").","; + $sql .= " fk_projet=".(isset($this->fk_project) ? (int) $this->fk_project : "null").","; + $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? (int) $this->cond_reglement_id : "null").","; + $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 .= " date_livraison=".(strval($this->delivery_date) != '' ? "'".$this->db->idate($this->delivery_date)."'" : 'null').","; - $sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? $this->shipping_method_id : "null").","; - $sql .= " fk_account=".($this->fk_account > 0 ? $this->fk_account : "null").","; - $sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? $this->demand_reason_id : "null").","; + $sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? (int) $this->shipping_method_id : "null").","; + $sql .= " fk_account=".($this->fk_account > 0 ? (int) $this->fk_account : "null").","; + $sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? (int) $this->demand_reason_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").","; - $sql .= " fk_warehouse=".($this->warehouse_id > 0 ? $this->warehouse_id : "null").","; + $sql .= " fk_warehouse=".($this->warehouse_id > 0 ? (int) $this->warehouse_id : "null").","; $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null").","; $sql .= " module_source = ".(isset($this->module_source) ? "'".$this->db->escape($this->module_source)."'" : "null").","; $sql .= " pos_source = ".(isset($this->pos_source) ? "'".$this->db->escape($this->pos_source)."'" : "null"); diff --git a/htdocs/societe/class/societe.class.php b/htdocs/societe/class/societe.class.php index 523fed76717..5cada037c64 100644 --- a/htdocs/societe/class/societe.class.php +++ b/htdocs/societe/class/societe.class.php @@ -1657,7 +1657,7 @@ class Societe extends CommonObject $sql .= ",socialnetworks = '".$this->db->escape(json_encode($this->socialnetworks))."'"; $sql .= ",url = ".(!empty($this->url) ? "'".$this->db->escape($this->url)."'" : "null"); - $sql .= ",parent = ".($this->parent > 0 ? $this->parent : "null"); + $sql .= ",parent = ".($this->parent > 0 ? (int) $this->parent : "null"); $sql .= ",note_private = ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null"); $sql .= ",note_public = ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null"); diff --git a/htdocs/user/class/user.class.php b/htdocs/user/class/user.class.php index 9b47ff3731b..b1a20594e01 100644 --- a/htdocs/user/class/user.class.php +++ b/htdocs/user/class/user.class.php @@ -2291,10 +2291,10 @@ class User extends CommonObject if (!empty($user->admin) && empty($user->entity) && $user->id != $this->id) { $sql .= ", entity = ".((int) $this->entity); // entity flag can be set/unset only by an another superadmin user } - $sql .= ", default_range = ".($this->default_range > 0 ? $this->default_range : 'null'); - $sql .= ", default_c_exp_tax_cat = ".($this->default_c_exp_tax_cat > 0 ? $this->default_c_exp_tax_cat : 'null'); - $sql .= ", fk_warehouse = ".($this->fk_warehouse > 0 ? $this->fk_warehouse : "null"); - $sql .= ", fk_establishment = ".($this->fk_establishment > 0 ? $this->fk_establishment : "null"); + $sql .= ", default_range = ".($this->default_range > 0 ? (int) $this->default_range : 'null'); + $sql .= ", default_c_exp_tax_cat = ".($this->default_c_exp_tax_cat > 0 ? (int) $this->default_c_exp_tax_cat : 'null'); + $sql .= ", fk_warehouse = ".($this->fk_warehouse > 0 ? (int) $this->fk_warehouse : "null"); + $sql .= ", fk_establishment = ".($this->fk_establishment > 0 ? (int) $this->fk_establishment : "null"); $sql .= ", lang = ".($this->lang ? "'".$this->db->escape($this->lang)."'" : "null"); $sql .= " WHERE rowid = ".((int) $this->id); From 7f39b0cced8c30a286c7264df70a34fab048470d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20FRANCE?= Date: Tue, 11 Aug 2026 02:13:43 +0200 Subject: [PATCH 07/15] fix CI in v23 (#39439) * fix CI * Update mod_holiday_madonna.php * Update lettering.class.php * Update modExpenseReport.class.php * Update modFacture.class.php * Update modFicheinter.class.php * Update modFournisseur.class.php * Update modHoliday.class.php * Update modMailing.class.php * Update modPrelevement.class.php * Update modPropale.class.php * Update modReception.class.php * Update modSociete.class.php * Update modStock.class.php * Update modSupplierProposal.class.php * Update myobject_agenda.php --- htdocs/accountancy/class/lettering.class.php | 2 +- .../holiday/mod_holiday_immaculate.php | 3 + .../modules/holiday/mod_holiday_madonna.php | 3 + .../core/modules/modExpenseReport.class.php | 60 ++++++------- htdocs/core/modules/modFacture.class.php | 46 +++++----- htdocs/core/modules/modFicheinter.class.php | 37 +++++--- htdocs/core/modules/modFournisseur.class.php | 87 +++++++++--------- htdocs/core/modules/modHoliday.class.php | 49 +++++----- htdocs/core/modules/modMailing.class.php | 17 ++-- htdocs/core/modules/modPrelevement.class.php | 19 ++-- htdocs/core/modules/modPropale.class.php | 90 +++++++++---------- htdocs/core/modules/modReception.class.php | 61 +++++++------ htdocs/core/modules/modSociete.class.php | 90 +++++++++---------- htdocs/core/modules/modStock.class.php | 68 +++++++------- .../modules/modSupplierProposal.class.php | 45 +++++----- .../template/myobject_agenda.php | 2 +- 16 files changed, 352 insertions(+), 327 deletions(-) diff --git a/htdocs/accountancy/class/lettering.class.php b/htdocs/accountancy/class/lettering.class.php index be49eefe6c0..ee6e313b628 100644 --- a/htdocs/accountancy/class/lettering.class.php +++ b/htdocs/accountancy/class/lettering.class.php @@ -910,7 +910,7 @@ class Lettering extends BookKeeping /** * Get element ids grouped by link or element in common * - * @param array> $link_by_element List of payment ids by link key + * @param array> $link_by_element List of payment ids by link key * @param array> $element_by_link List of element ids by link key * @param string $link_key Link key (used for recursive function) * @param array $current_group Current group (used for recursive function) diff --git a/htdocs/core/modules/holiday/mod_holiday_immaculate.php b/htdocs/core/modules/holiday/mod_holiday_immaculate.php index c4fb76d384b..8b60293566f 100644 --- a/htdocs/core/modules/holiday/mod_holiday_immaculate.php +++ b/htdocs/core/modules/holiday/mod_holiday_immaculate.php @@ -36,6 +36,9 @@ class mod_holiday_immaculate extends ModelNumRefHolidays public $name = 'Immaculate'; public $version = 'dolibarr'; + /** + * @var int position + */ public $position = 100; diff --git a/htdocs/core/modules/holiday/mod_holiday_madonna.php b/htdocs/core/modules/holiday/mod_holiday_madonna.php index 2f40ffd4fc9..8c8960c369b 100644 --- a/htdocs/core/modules/holiday/mod_holiday_madonna.php +++ b/htdocs/core/modules/holiday/mod_holiday_madonna.php @@ -35,6 +35,9 @@ class mod_holiday_madonna extends ModelNumRefHolidays public $name = 'Madonna'; public $version = 'dolibarr'; + /** + * @var int position + */ public $position = 10; diff --git a/htdocs/core/modules/modExpenseReport.class.php b/htdocs/core/modules/modExpenseReport.class.php index d503a83e614..eac390d0f44 100644 --- a/htdocs/core/modules/modExpenseReport.class.php +++ b/htdocs/core/modules/modExpenseReport.class.php @@ -73,36 +73,36 @@ class modExpenseReport extends DolibarrModules $this->langfiles = array("companies", "trips"); // Constants - $this->const = array(); // List of particular constants to add when module is enabled (key, 'chaine', value, desc, visible, 0 or 'allentities') - $r = 0; - - $this->const[$r][0] = "EXPENSEREPORT_ADDON_PDF"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "standard"; - $this->const[$r][3] = 'Name of manager to build PDF expense reports documents'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "EXPENSEREPORT_ADDON"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "mod_expensereport_jade"; - $this->const[$r][3] = 'Name of manager to generate expense report ref number'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "MAIN_DELAY_EXPENSEREPORTS"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "15"; - $this->const[$r][3] = 'Tolerance delay (in days) before alert for expense reports to approve'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "MAIN_DELAY_EXPENSEREPORTS_TO_PAY"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "15"; - $this->const[$r][3] = 'Tolerance delay (in days) before alert for expense reports to pay'; - $this->const[$r][4] = 0; - $r++; + $this->const = [ // List of particular constants to add when module is enabled (key, 'chaine', value, desc, visible, 0 or 'allentities') + [ + "EXPENSEREPORT_ADDON_PDF", + "chaine", + "standard", + 'Name of manager to build PDF expense reports documents', + 0, + ], + [ + "EXPENSEREPORT_ADDON", + "chaine", + "mod_expensereport_jade", + 'Name of manager to generate expense report ref number', + 0, + ], + [ + "MAIN_DELAY_EXPENSEREPORTS", + "chaine", + "15", + 'Tolerance delay (in days) before alert for expense reports to approve', + 0, + ], + [ + "MAIN_DELAY_EXPENSEREPORTS_TO_PAY", + "chaine", + "15", + 'Tolerance delay (in days) before alert for expense reports to pay', + 0, + ], + ]; // Array to add new pages in new tabs $this->tabs[] = array(); diff --git a/htdocs/core/modules/modFacture.class.php b/htdocs/core/modules/modFacture.class.php index cd0127756fa..4275a1b3141 100644 --- a/htdocs/core/modules/modFacture.class.php +++ b/htdocs/core/modules/modFacture.class.php @@ -76,29 +76,29 @@ class modFacture extends DolibarrModules $this->config_page_url = array("invoice.php"); // Constants - $this->const = array(); - $r = 0; - - $this->const[$r][0] = "FACTURE_ADDON"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "mod_facture_terre"; - $this->const[$r][3] = 'Name of numbering numerotation rules of invoice'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "FACTURE_ADDON_PDF"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "sponge"; - $this->const[$r][3] = 'Name of PDF model of invoice'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "FACTURE_ADDON_PDF_ODT_PATH"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/invoices"; - $this->const[$r][3] = ""; - $this->const[$r][4] = 0; - $r++; + $this->const = [ + [ + "FACTURE_ADDON", + "chaine", + "mod_facture_terre", + 'Name of numbering numerotation rules of invoice', + 0, + ], + [ + "FACTURE_ADDON_PDF", + "chaine", + "sponge", + 'Name of PDF model of invoice', + 0, + ], + [ + "FACTURE_ADDON_PDF_ODT_PATH", + "chaine", + "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/invoices", + "", + 0, + ], + ]; // Boxes //$this->boxes = array(0=>array(1=>'box_factures_imp.php'),1=>array(1=>'box_factures.php')); diff --git a/htdocs/core/modules/modFicheinter.class.php b/htdocs/core/modules/modFicheinter.class.php index 8c0b9bb3b5c..96579ce90c9 100644 --- a/htdocs/core/modules/modFicheinter.class.php +++ b/htdocs/core/modules/modFicheinter.class.php @@ -72,24 +72,35 @@ class modFicheinter extends DolibarrModules // Config pages $this->config_page_url = array("fichinter.php"); - // Constants - $this->const = array(); - $r = 0; - if (!isset($conf->ficheinter) || !isset($conf->ficheinter->enabled)) { $conf->ficheinter = new stdClass(); $conf->ficheinter->enabled = 0; } - $this->const[$r][0] = "FICHEINTER_ADDON_PDF"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "soleil"; - $r++; - - $this->const[$r][0] = "FICHEINTER_ADDON"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "pacific"; - $r++; + // Constants + // 0:name + // 1:type + // 2:val + // 3:note + // 4:visible + // 5:entity + // 6:deleteonunactive + $this->const = [ + [ + "FICHEINTER_ADDON_PDF", + "chaine", + "soleil", + "", + 0, + ], + [ + "FICHEINTER_ADDON", + "chaine", + "pacific", + "", + 0, + ], + ]; // Boxes $this->boxes = array(0=>array('file'=>'box_ficheinter.php', 'enabledbydefaulton'=>'Home')); diff --git a/htdocs/core/modules/modFournisseur.class.php b/htdocs/core/modules/modFournisseur.class.php index 46355ca4cb9..56e0f0fb692 100644 --- a/htdocs/core/modules/modFournisseur.class.php +++ b/htdocs/core/modules/modFournisseur.class.php @@ -82,55 +82,62 @@ class modFournisseur extends DolibarrModules $this->config_page_url = array("supplier_order.php"); // Constants - $this->const = array(); - $r = 0; - - $this->const[$r][0] = "COMMANDE_SUPPLIER_ADDON_PDF"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "cornas"; - $this->const[$r][3] = 'Nom du gestionnaire de generation des bons de commande en PDF'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "COMMANDE_SUPPLIER_ADDON_NUMBER"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "mod_commande_fournisseur_muguet"; - $this->const[$r][3] = 'Nom du gestionnaire de numerotation des commandes fournisseur'; - $this->const[$r][4] = 0; - $r++; + // 0:name + // 1:type + // 2:val + // 3:note + // 4:visible + // 5:entity + // 6:deleteonunactive + $this->const = [ + [ + "COMMANDE_SUPPLIER_ADDON_PDF", + "chaine", + "cornas", + 'Nom du gestionnaire de generation des bons de commande en PDF', + 0, + ], + [ + "COMMANDE_SUPPLIER_ADDON_NUMBER", + "chaine", + "mod_commande_fournisseur_muguet", + 'Nom du gestionnaire de numerotation des commandes fournisseur', + 0, + ], + [ + "INVOICE_SUPPLIER_ADDON_NUMBER", + "chaine", + "mod_facture_fournisseur_cactus", + 'Nom du gestionnaire de numerotation des factures fournisseur', + 0, + ], + [ + // Add ability ODT for Supplier orders + "SUPPLIER_ORDER_ADDON_PDF_ODT_PATH", + "chaine", + "DOL_DATA_ROOT" . ($conf->entity > 1 ? '/' . $conf->entity : '') . "/doctemplates/supplier_orders", + '', + 0, + ], + [ + // Add ability ODT for Supplier Invoices + "SUPPLIER_INVOICE_ADDON_PDF_ODT_PATH", + "chaine", + "DOL_DATA_ROOT" . ($conf->entity > 1 ? '/' . $conf->entity : '') . "/doctemplates/supplier_invoices", + "", + 0, + ], + ]; /* For supplier invoice, we must not have default pdf template on. In most cases, we need to join PDF from supplier, not have a document generated. $this->const[$r][0] = "INVOICE_SUPPLIER_ADDON_PDF"; $this->const[$r][1] = "chaine"; $this->const[$r][2] = "canelle"; - $this->const[$r][3] = 'Nom du gestionnaire de generation des factures fournisseur en PDF'; + $this->const[$r][3] = 'Name of the generator for PDF supplier invoices'; $this->const[$r][4] = 0; $r++; */ - $this->const[$r][0] = "INVOICE_SUPPLIER_ADDON_NUMBER"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "mod_facture_fournisseur_cactus"; - $this->const[$r][3] = 'Nom du gestionnaire de numerotation des factures fournisseur'; - $this->const[$r][4] = 0; - $r++; - - // Add ability ODT for Supplier orders - $this->const[$r][0] = "SUPPLIER_ORDER_ADDON_PDF_ODT_PATH"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/supplier_orders"; - $this->const[$r][3] = ''; - $this->const[$r][4] = 0; - $r++; - - // Add ability ODT for Supplier Invoices - $this->const[$r][0] = "SUPPLIER_INVOICE_ADDON_PDF_ODT_PATH"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/supplier_invoices"; - $this->const[$r][3] = ""; - $this->const[$r][4] = 0; - $r++; - // Boxes $this->boxes = array( 0 => array('file' => 'box_graph_invoices_supplier_permonth.php', 'enabledbydefaulton' => 'Home'), diff --git a/htdocs/core/modules/modHoliday.class.php b/htdocs/core/modules/modHoliday.class.php index fd351ee1104..f5074a7f037 100644 --- a/htdocs/core/modules/modHoliday.class.php +++ b/htdocs/core/modules/modHoliday.class.php @@ -90,31 +90,30 @@ class modHoliday extends DolibarrModules $this->langfiles = array("holiday"); // Constants - // Example: $this->const=array(0=>array('MYMODULE_MYNEWCONST1','chaine','myvalue','This is a constant to add',0), - // 1=>array('MYMODULE_MYNEWCONST2','chaine','myvalue','This is another constant to add',0) ); - $this->const = array(); // List of particular constants to add when module is enabled (key, 'chaine', value, desc, visible, 0 or 'allentities') - $r = 0; - - $this->const[$r][0] = "HOLIDAY_ADDON"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "mod_holiday_madonna"; - $this->const[$r][3] = 'Nom du gestionnaire de numerotation des congés'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "HOLIDAY_ADDON_PDF"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "celebrate"; - $this->const[$r][3] = 'Name of PDF model of holiday'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "HOLIDAY_ADDON_PDF_ODT_PATH"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/holiday"; - $this->const[$r][3] = ""; - $this->const[$r][4] = 0; - $r++; + // List of particular constants to add when module is enabled (key, 'chaine', value, desc, visible, 0 or 'allentities') + $this->const = [ + [ + "HOLIDAY_ADDON", + "chaine", + "mod_holiday_madonna", + 'Nom du gestionnaire de numerotation des congés', + 0, + ], + [ + "HOLIDAY_ADDON_PDF", + "chaine", + "celebrate", + 'Name of PDF model of holiday', + 0, + ], + [ + "HOLIDAY_ADDON_PDF_ODT_PATH", + "chaine", + "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/holiday", + "", + 0, + ], + ]; // Array to add new pages in new tabs //$this->tabs[] = array('data'=>'user:+paidholidays:CPTitreMenu:holiday:$user->rights->holiday->read:/holiday/list.php?mainmenu=hrm&id=__ID__'); // We avoid to get one tab for each module. RH data are already in RH tab. diff --git a/htdocs/core/modules/modMailing.class.php b/htdocs/core/modules/modMailing.class.php index 626c8e7c0ca..8f722d2b967 100644 --- a/htdocs/core/modules/modMailing.class.php +++ b/htdocs/core/modules/modMailing.class.php @@ -72,15 +72,16 @@ class modMailing extends DolibarrModules $this->config_page_url = array("mailing.php"); // Constants - $this->const = array(); - $r = 0; + $this->const = [ + [ + "MAILING_CONTACT_DEFAULT_BULK_STATUS", + "chaine", + "0", + 'Default value for field "Refuse bulk email" when creating a contact', + 0, + ], + ]; - $this->const[$r][0] = "MAILING_CONTACT_DEFAULT_BULK_STATUS"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "0"; - $this->const[$r][3] = 'Default value for field "Refuse bulk email" when creating a contact'; - $this->const[$r][4] = 0; - $r++; // Boxes $this->boxes = array(); diff --git a/htdocs/core/modules/modPrelevement.class.php b/htdocs/core/modules/modPrelevement.class.php index dd6ccd484fa..31df34a6328 100644 --- a/htdocs/core/modules/modPrelevement.class.php +++ b/htdocs/core/modules/modPrelevement.class.php @@ -73,16 +73,15 @@ class modPrelevement extends DolibarrModules $this->config_page_url = array("prelevement.php"); // Constants - $this->const = array(); - $r = 0; - - $this->const[$r][0] = "BANK_ADDON_PDF"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "sepamandate"; - $this->const[$r][3] = 'Name of manager to generate SEPA mandate'; - $this->const[$r][4] = 0; - $r++; - + $this->const = [ + [ + "BANK_ADDON_PDF", + "chaine", + "sepamandate", + 'Name of manager to generate SEPA mandate', + 0, + ], + ]; // Boxes $this->boxes = array(); diff --git a/htdocs/core/modules/modPropale.class.php b/htdocs/core/modules/modPropale.class.php index 571e7223ddc..262585f83b0 100644 --- a/htdocs/core/modules/modPropale.class.php +++ b/htdocs/core/modules/modPropale.class.php @@ -74,53 +74,51 @@ class modPropale extends DolibarrModules $this->langfiles = array("propal", "bills", "companies", "sendings", "products"); // Constants - $this->const = array(); - $r = 0; - - $this->const[$r][0] = "PROPALE_ADDON_PDF"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "cyan"; - $this->const[$r][3] = 'Name of the proposal generation manager in PDF format'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "PROPALE_ADDON"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "mod_propale_marbre"; - $this->const[$r][3] = 'Name of proposal numbering manager'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "PROPALE_VALIDITY_DURATION"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "15"; - $this->const[$r][3] = 'Duration of validity of business proposals'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "PROPALE_ADDON_PDF_ODT_PATH"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/proposals"; - $this->const[$r][3] = ""; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "PROPOSAL_ALLOW_ONLINESIGN"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "1"; - $this->const[$r][3] = ""; - $this->const[$r][4] = 0; - $r++; - include_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php'; - $this->const[$r][0] = "PROPOSAL_ONLINE_SIGNATURE_SECURITY_TOKEN"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = getRandomPassword(true); - $this->const[$r][3] = ""; - $this->const[$r][4] = 0; - $r++; - - + $this->const = [ + [ + "PROPALE_ADDON_PDF", + "chaine", + "cyan", + 'Name of the proposal generation manager in PDF format', + 0, + ], + [ + "PROPALE_ADDON", + "chaine", + "mod_propale_marbre", + 'Name of proposal numbering manager', + 0, + ], + [ + "PROPALE_VALIDITY_DURATION", + "chaine", + "15", + 'Duration of validity of business proposals', + 0, + ], + [ + "PROPALE_ADDON_PDF_ODT_PATH", + "chaine", + "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/proposals", + "", + 0, + ], + [ + "PROPOSAL_ALLOW_ONLINESIGN", + "chaine", + "1", + "", + 0, + ], + [ + "PROPOSAL_ONLINE_SIGNATURE_SECURITY_TOKEN", + "chaine", + getRandomPassword(true), + "", + 0, + ], + ]; /*$this->const[$r][0] = "PROPALE_DRAFT_WATERMARK"; $this->const[$r][2] = "__(Draft)__"; diff --git a/htdocs/core/modules/modReception.class.php b/htdocs/core/modules/modReception.class.php index 560ab4383f7..5ee3d58bf4e 100644 --- a/htdocs/core/modules/modReception.class.php +++ b/htdocs/core/modules/modReception.class.php @@ -71,38 +71,37 @@ class modReception extends DolibarrModules $this->requiredby = array(); $this->conflictwith = array(); $this->langfiles = array('receptions'); - // Constants - $this->const = array(); - $r = 0; - - $this->const[$r][0] = "RECEPTION_ADDON_PDF"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "squille"; - $this->const[$r][3] = 'Nom du gestionnaire de generation des bons receptions en PDF'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "RECEPTION_ADDON_NUMBER"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "mod_reception_beryl"; - $this->const[$r][3] = 'Name for numbering manager for receptions'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "RECEPTION_ADDON_PDF_ODT_PATH"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/receptions"; - $this->const[$r][3] = ""; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "MAIN_SUBMODULE_RECEPTION"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "1"; - $this->const[$r][3] = "Enable receptions"; - $this->const[$r][4] = 0; - $r++; + $this->const = [ + [ + "RECEPTION_ADDON_PDF", + "chaine", + "squille", + 'Nom du gestionnaire de generation des bons receptions en PDF', + 0, + ], + [ + "RECEPTION_ADDON_NUMBER", + "chaine", + "mod_reception_beryl", + 'Name for numbering manager for receptions', + 0, + ], + [ + "RECEPTION_ADDON_PDF_ODT_PATH", + "chaine", + "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/receptions", + "", + 0, + ], + [ + "MAIN_SUBMODULE_RECEPTION", + "chaine", + "1", + "Enable receptions", + 0, + ], + ]; // Boxes $this->boxes = array(); diff --git a/htdocs/core/modules/modSociete.class.php b/htdocs/core/modules/modSociete.class.php index b7e43ca91f9..e14f2038fe5 100644 --- a/htdocs/core/modules/modSociete.class.php +++ b/htdocs/core/modules/modSociete.class.php @@ -76,52 +76,50 @@ class modSociete extends DolibarrModules $this->langfiles = array("companies", 'bills', "compta", "admin", "banks"); // Constants - $this->const = array(); - $r = 0; - - $this->const[$r][0] = "SOCIETE_CODECLIENT_ADDON"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "mod_codeclient_monkey"; - $this->const[$r][3] = 'Module to control third parties codes'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "SOCIETE_CODECOMPTA_ADDON"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "mod_codecompta_panicum"; - $this->const[$r][3] = 'Module to control third parties codes'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "SOCIETE_FISCAL_MONTH_START"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "1"; - $this->const[$r][3] = "Enter the month number of the first month of the fiscal year, e. g. 9 for September"; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "COMPANY_ADDON_PDF_ODT_PATH"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/thirdparties"; - $this->const[$r][3] = ""; - $this->const[$r][4] = 0; - $r++; - - /* - $this->const[$r][0] = "COMPANY_HIDE_INACTIVE_IN_COMBOBOX"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "0"; - $this->const[$r][3] = "hide thirdparty customer inative in combobox"; - $this->const[$r][4] = 1; - $r++; - */ - - $this->const[$r][0] = "SOCIETE_ADD_REF_IN_LIST"; - $this->const[$r][1] = "yesno"; - $this->const[$r][2] = "0"; - $this->const[$r][3] = "Display customer ref into select list"; - $this->const[$r][4] = 0; - $r++; + $this->const = [ + [ + "SOCIETE_CODECLIENT_ADDON", + "chaine", + "mod_codeclient_monkey", + 'Module to control third parties codes', + 0, + ], + [ + "SOCIETE_CODECOMPTA_ADDON", + "chaine", + "mod_codecompta_panicum", + 'Module to control third parties codes', + 0, + ], + [ + "SOCIETE_FISCAL_MONTH_START", + "chaine", + "1", + "Enter the month number of the first month of the fiscal year, e. g. 9 for September", + 0, + ], + [ + "COMPANY_ADDON_PDF_ODT_PATH", + "chaine", + "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/thirdparties", + "", + 0, + ], + // [ + // "COMPANY_HIDE_INACTIVE_IN_COMBOBOX", + // "chaine", + // "0", + // "hide thirdparty customer inative in combobox", + // 1, + // ], + [ + "SOCIETE_ADD_REF_IN_LIST", + "yesno", + "0", + "Display customer ref into select list", + 0, + ], + ]; // Boxes $this->boxes = array( diff --git a/htdocs/core/modules/modStock.class.php b/htdocs/core/modules/modStock.class.php index f8bddb6c297..c630cbdd1cc 100644 --- a/htdocs/core/modules/modStock.class.php +++ b/htdocs/core/modules/modStock.class.php @@ -75,38 +75,44 @@ class modStock extends DolibarrModules $this->langfiles = array("stocks"); // Constants - $this->const = array(); - $r = 0; + $this->const = [ + [ + 'STOCK_DISALLOW_NEGATIVE_TRANSFER', + 'chaine', + '1', + '', + 0, + ], + [ + "STOCK_ADDON_PDF", + "chaine", + "standard_stock", + 'Name of PDF model of stock', + 0, + ], + [ + "MOUVEMENT_ADDON_PDF", + "chaine", + "standard_movement_stock", + 'Name of PDF model of stock movement', + 0, + ], + [ + "STOCK_ADDON_PDF_ODT_PATH", + "chaine", + "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/stocks", + "", + 0, + ], + [ + "MOUVEMENT_ADDON_PDF_ODT_PATH", + "chaine", + "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/stocks/movements", + "", + 0, + ], + ]; - $this->const[$r] = array('STOCK_DISALLOW_NEGATIVE_TRANSFER', 'chaine', '1', '', 0); - - $r++; - $this->const[$r][0] = "STOCK_ADDON_PDF"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "standard_stock"; - $this->const[$r][3] = 'Name of PDF model of stock'; - $this->const[$r][4] = 0; - - $r++; - $this->const[$r][0] = "MOUVEMENT_ADDON_PDF"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "standard_movement_stock"; - $this->const[$r][3] = 'Name of PDF model of stock movement'; - $this->const[$r][4] = 0; - - $r++; - $this->const[$r][0] = "STOCK_ADDON_PDF_ODT_PATH"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/stocks"; - $this->const[$r][3] = ""; - $this->const[$r][4] = 0; - - $r++; - $this->const[$r][0] = "MOUVEMENT_ADDON_PDF_ODT_PATH"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/stocks/movements"; - $this->const[$r][3] = ""; - $this->const[$r][4] = 0; // Boxes $this->boxes = array(); diff --git a/htdocs/core/modules/modSupplierProposal.class.php b/htdocs/core/modules/modSupplierProposal.class.php index 82151610ffa..91fd081b67e 100644 --- a/htdocs/core/modules/modSupplierProposal.class.php +++ b/htdocs/core/modules/modSupplierProposal.class.php @@ -74,28 +74,29 @@ class modSupplierProposal extends DolibarrModules $this->langfiles = array("supplier_proposal"); // Constants - $this->const = array(); - $r = 0; - - $this->const[$r][0] = "SUPPLIER_PROPOSAL_ADDON_PDF"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "aurore"; - $this->const[$r][3] = 'Name of submodule to generate PDF for supplier quotation request'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "SUPPLIER_PROPOSAL_ADDON"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "mod_supplier_proposal_marbre"; - $this->const[$r][3] = 'Name of submodule to number supplier quotation request'; - $this->const[$r][4] = 0; - $r++; - - $this->const[$r][0] = "SUPPLIER_PROPOSAL_ADDON_PDF_ODT_PATH"; - $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/supplier_proposals"; - $this->const[$r][3] = ""; - $this->const[$r][4] = 0; + $this->const = [ + [ + "SUPPLIER_PROPOSAL_ADDON_PDF", + "chaine", + "aurore", + 'Name of submodule to generate PDF for supplier quotation request', + 0, + ], + [ + "SUPPLIER_PROPOSAL_ADDON", + "chaine", + "mod_supplier_proposal_marbre", + 'Name of submodule to number supplier quotation request', + 0, + ], + [ + "SUPPLIER_PROPOSAL_ADDON_PDF_ODT_PATH", + "chaine", + "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/supplier_proposals", + "", + 0, + ], + ]; // Boxes $this->boxes = array(); diff --git a/htdocs/modulebuilder/template/myobject_agenda.php b/htdocs/modulebuilder/template/myobject_agenda.php index 712735c0328..b93a08f2ede 100644 --- a/htdocs/modulebuilder/template/myobject_agenda.php +++ b/htdocs/modulebuilder/template/myobject_agenda.php @@ -339,7 +339,7 @@ if ($object->id > 0) { $param .= '&search_complete='.urlencode($search_complete); } if ($search_filtert != '') { - $param .= '&search_filtert='.urlencode($search_filtert); + $param .= '&search_filtert='.urlencode((string) $search_filtert); } if ($search_dateevent_start != '') { $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); From eec0a49a874c7617e32558a61e9d11f02ee4a961 Mon Sep 17 00:00:00 2001 From: atm-lucas <121817516+atm-lucasmantegari@users.noreply.github.com> Date: Tue, 11 Aug 2026 02:25:58 +0200 Subject: [PATCH 08/15] Renaming a product's attached file fails when PRODUCT_USE_OLD_PATH_FOR_PHOTO is enabled (#39417) --- htdocs/core/actions_linkedfiles.inc.php | 17 ++++++++++++----- htdocs/core/lib/files.lib.php | 6 ++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/htdocs/core/actions_linkedfiles.inc.php b/htdocs/core/actions_linkedfiles.inc.php index b02b42e66ba..aeeb2e3b60b 100644 --- a/htdocs/core/actions_linkedfiles.inc.php +++ b/htdocs/core/actions_linkedfiles.inc.php @@ -268,24 +268,31 @@ if ($action == 'confirm_deletefile' && $confirm == 'yes' && !empty($permissionto } if (empty($error) && $filenamefrom != $filenameto) { + // For backward compatibility, the file to rename may be stored into an old path (see PRODUCT_USE_OLD_PATH_FOR_PHOTO). + // Upload and delete actions already fall back on $upload_dirold, so the rename action must do the same. + $dirforfile = $upload_dir; + if (!empty($upload_dirold) && !dol_is_file($upload_dir.'/'.$filenamefrom) && dol_is_file($upload_dirold.'/'.$filenamefrom)) { + $dirforfile = $upload_dirold; + } + // Security: // Disallow file with some extensions. We rename them. // Because if we put the documents directory into a directory inside web root (very bad), this allows to execute on demand arbitrary code. if (isAFileWithExecutableContent($filenameto) && !getDolGlobalString('MAIN_DOCUMENT_IS_OUTSIDE_WEBROOT_SO_NOEXE_NOT_REQUIRED')) { - // $upload_dir ends with a slash, so be must be sure the medias dir to compare to ends with slash too. + // $dirforfile ends with a slash, so be must be sure the medias dir to compare to ends with slash too. $publicmediasdirwithslash = $conf->medias->multidir_output[$conf->entity]; if (!preg_match('/\/$/', $publicmediasdirwithslash)) { $publicmediasdirwithslash .= '/'; } - if (strpos($upload_dir, $publicmediasdirwithslash) !== 0) { // We never add .noexe on files into media directory + if (strpos($dirforfile, $publicmediasdirwithslash) !== 0) { // We never add .noexe on files into media directory $filenameto .= '.noexe'; } } if ($filenamefrom && $filenameto) { - $srcpath = $upload_dir.'/'.$filenamefrom; - $destpath = $upload_dir.'/'.$filenameto; + $srcpath = $dirforfile.'/'.$filenamefrom; + $destpath = $dirforfile.'/'.$filenameto; /* disabled. Too many bugs. All files of an object must remain into directory of object. link with event should be done in llx_ecm_files with column agenda_id. if ($modulepart == "ticket" && !dol_is_file($srcpath)) { $srcbis = $conf->agenda->dir_output.'/'.GETPOST('section_dir').$filenamefrom; @@ -296,7 +303,7 @@ if ($action == 'confirm_deletefile' && $confirm == 'yes' && !empty($permissionto }*/ $reshook = $hookmanager->initHooks(array('actionlinkedfiles')); - $parameters = array('filenamefrom' => $filenamefrom, 'filenameto' => $filenameto, 'upload_dir' => $upload_dir); + $parameters = array('filenamefrom' => $filenamefrom, 'filenameto' => $filenameto, 'upload_dir' => $dirforfile); $reshook = $hookmanager->executeHooks('renameUploadedFile', $parameters, $object); if (empty($reshook)) { diff --git a/htdocs/core/lib/files.lib.php b/htdocs/core/lib/files.lib.php index cd4ad6885aa..36c500008bb 100644 --- a/htdocs/core/lib/files.lib.php +++ b/htdocs/core/lib/files.lib.php @@ -439,7 +439,8 @@ function completeFileArrayWithDatabaseInfo(&$filearray, $relativedir, $object = $filearrayindatabase = dol_dir_list_in_database(rtrim($relativedir, "/\\"), '', null, 'name', SORT_ASC, 0, '', $object); global $modulepart; - if ($modulepart == 'produit' && getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) { + // Note: $modulepart is 'product' when set by product/document.php, but 'produit' in some other contexts, so we accept both. + if (in_array($modulepart, array('produit', 'product')) && getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) { // TODO Remove this when PRODUCT_USE_OLD_PATH_FOR_PHOTO will be removed global $object; if (!empty($object->id)) { @@ -452,7 +453,8 @@ function completeFileArrayWithDatabaseInfo(&$filearray, $relativedir, $object = $relativedirold = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dirold); $relativedirold = ltrim($relativedirold, "/\\"); - $filearrayindatabase = array_merge($filearrayindatabase, dol_dir_list_in_database($relativedirold, '', null, 'name', SORT_ASC)); + // Note: $object must be provided so the entity filter matches the one used to forge $upload_dirold (multicompany) + $filearrayindatabase = array_merge($filearrayindatabase, dol_dir_list_in_database($relativedirold, '', null, 'name', SORT_ASC, 0, '', $object)); } } elseif ($modulepart == 'ticket') { foreach ($filearray as $key => $val) { From 5028dae981e47dfab349f8fe2ef79b54ae95d1b6 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 12 Aug 2026 22:10:23 +0200 Subject: [PATCH 09/15] Fix file access with hasp parameter - reported by tremor hunter --- htdocs/document.php | 4 ++-- htdocs/viewimage.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/document.php b/htdocs/document.php index 2b4e0be1f65..306011ce52c 100644 --- a/htdocs/document.php +++ b/htdocs/document.php @@ -185,8 +185,8 @@ if (in_array($modulepart, array('facture_paiement', 'unpaid'))) { // If we have a hash public (hashp), we guess the original_file. $ecmfile = ''; -if (!empty($hashp)) { - if (GETPOST('type', 'alpha')=='link') { +if (!empty($hashp) && $hashp != 'shared') { + if (GETPOST('type', 'alpha') == 'link') { require_once DOL_DOCUMENT_ROOT.'/core/class/link.class.php'; $link = new Link($db); $result = $link->fetch(0, $hashp); diff --git a/htdocs/viewimage.php b/htdocs/viewimage.php index 76149763aa4..07d388c7ba3 100644 --- a/htdocs/viewimage.php +++ b/htdocs/viewimage.php @@ -213,7 +213,7 @@ if ($cachestring) { } // If we have a hash public (hashp), we guess the original_file. -if (!empty($hashp)) { +if (!empty($hashp) && $hashp != 'shared') { include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php'; include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php'; $ecmfile = new EcmFiles($db); From a1bfd5d9fbabacd4b3daa91bd0923ec8f2e39080 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Wed, 12 Aug 2026 23:07:45 +0200 Subject: [PATCH 10/15] fix: module builder rebuild class mistype foreignkey so delete them (#39480) * fix: module builder rebuild class mistype foreignkey so delete them * fix: module builder rebuild class mistype foreignkey so delete them --- htdocs/core/lib/modulebuilder.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/lib/modulebuilder.lib.php b/htdocs/core/lib/modulebuilder.lib.php index 78855d41a5e..8233391cdd4 100644 --- a/htdocs/core/lib/modulebuilder.lib.php +++ b/htdocs/core/lib/modulebuilder.lib.php @@ -156,7 +156,7 @@ function rebuildObjectClass($destdir, $module, $objectname, $newmask, $readdir = $texttoinsert .= ' "index" => "'.(int) $val['index'].'",'; } if (!empty($val['foreignkey'])) { - $texttoinsert .= ' "foreignkey" => "'.(int) $val['foreignkey'].'",'; + $texttoinsert .= ' "foreignkey" => "'.dol_escape_php($val['foreignkey']).'",'; } if (!empty($val['searchall'])) { $texttoinsert .= ' "searchall" => "'.(int) $val['searchall'].'",'; From 3ad376dc014b0ef2d7005787ac100e5a3c287ebd Mon Sep 17 00:00:00 2001 From: VIAL-GOUTEYRON Quentin Date: Thu, 13 Aug 2026 02:45:02 +0200 Subject: [PATCH 11/15] FIX Holiday shown one day off in agenda for users in another timezone (#39465) Columns llx_holiday.date_debut and date_fin are pure DATE columns: they hold a calendar fact, not an absolute instant, so they carry no time and no timezone. The agenda read them with jdate() defaulting to 'tzserver', then projected them back with dol_print_date(..., 'tzuserrel') to pick the calendar day box. The resulting shift equals TZ_user - TZ_server, so any user whose timezone differs from the server got leaves rendered on the wrong day. Users aligned with the server saw nothing wrong, which is why this went unnoticed. It also triggers with no user timezone set at all, since 'tzuserrel' falls back to UTC when $_SESSION['dol_tz_string'] is empty, shifting leaves on any server east of UTC. Read and project those dates in GMT instead. This follows the documented core convention for dates without time ("We suppose dates without time are always gmt (storage of course + output)", commonobject.class.php; "For date without hour, date is always GMT for storage and output", extrafields.class.php) and honors the $daykey contract of $eventarray, which must be midnight GMT of the displayed calendar day. It also makes the half-day comparison consistent, since dol_get_first_hour() is already called with 'gmt' a few lines below and only supports gmt/tzserver anyway. Two further defects disappear as a side effect: the loop condition "while ($daykey <= $event->date_end_in_calendar)" compared a user-shifted $daykey against a server-anchored $datef and rendered one extra day box, and halfday=-1 happened to be correct while halfday=0/1 were not, because the +12h offset accidentally compensated the timezone shift. Regular events are untouched: llx_actioncomm.datep is a DATETIME holding a real instant, and its block still uses 'tzuserrel', so a 9am meeting stays correctly shifted for a viewer in another timezone. Every other render path already excludes type_code 'HOLIDAY', so there is no display change elsewhere. peruser.php carries a copy of the same block and is fixed identically, otherwise the two views of the same page would contradict each other. Tested with a real browser (Playwright) against a server running in UTC, driving the month view as Europe/Paris, America/Cayenne and Pacific/Auckland users on three existing leaves: 3 failures before, none after, with identical day boxes in every timezone. A single-day leave of 2026-07-28 was rendered on both 2026-07-27 and 2026-07-28 for the America/Cayenne user before the fix. --- htdocs/comm/action/index.php | 18 ++++++++++-------- htdocs/comm/action/peruser.php | 18 ++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/htdocs/comm/action/index.php b/htdocs/comm/action/index.php index 00c72caf900..7fc43f9ecf4 100644 --- a/htdocs/comm/action/index.php +++ b/htdocs/comm/action/index.php @@ -1207,8 +1207,10 @@ if ($user->hasRight("holiday", "read")) { $event->type = 'holiday'; $event->type_picto = 'holiday'; - $event->datep = $db->jdate($obj->date_start) + (empty($obj->halfday) || $obj->halfday == 1 ? 0 : 12) * 60 * 60; - $event->datef = $db->jdate($obj->date_end) + (empty($obj->halfday) || $obj->halfday == -1 ? 24 : 12) * 60 * 60 - 1; + // date_debut and date_fin are dates without time, so they must be read and rendered in GMT to + // stay independent from the server and user timezones (otherwise the calendar day box is shifted). + $event->datep = $db->jdate($obj->date_start, 'gmt') + (empty($obj->halfday) || $obj->halfday == 1 ? 0 : 12) * 60 * 60; + $event->datef = $db->jdate($obj->date_end, 'gmt') + (empty($obj->halfday) || $obj->halfday == -1 ? 24 : 12) * 60 * 60 - 1; $event->date_start_in_calendar = $event->datep; $event->date_end_in_calendar = $event->datef; @@ -1223,14 +1225,14 @@ if ($user->hasRight("holiday", "read")) { $event->label = $langs->trans("Holiday"); $daycursor = $event->date_start_in_calendar; - $annee = (int) dol_print_date($daycursor, '%Y', 'tzuserrel'); - $mois = (int) dol_print_date($daycursor, '%m', 'tzuserrel'); - $jour = (int) dol_print_date($daycursor, '%d', 'tzuserrel'); + $annee = (int) dol_print_date($daycursor, '%Y', 'gmt'); + $mois = (int) dol_print_date($daycursor, '%m', 'gmt'); + $jour = (int) dol_print_date($daycursor, '%d', 'gmt'); $daycursorend = $event->date_end_in_calendar; - $anneeend = (int) dol_print_date($daycursorend, '%Y', 'tzuserrel'); - $moisend = (int) dol_print_date($daycursorend, '%m', 'tzuserrel'); - $jourend = (int) dol_print_date($daycursorend, '%d', 'tzuserrel'); + $anneeend = (int) dol_print_date($daycursorend, '%Y', 'gmt'); + $moisend = (int) dol_print_date($daycursorend, '%m', 'gmt'); + $jourend = (int) dol_print_date($daycursorend, '%d', 'gmt'); // daykey must be date that represent day box in calendar so must be a user time $daykey = dol_mktime(0, 0, 0, $mois, $jour, $annee, 'gmt'); diff --git a/htdocs/comm/action/peruser.php b/htdocs/comm/action/peruser.php index 60a5a9822fe..948544654d7 100644 --- a/htdocs/comm/action/peruser.php +++ b/htdocs/comm/action/peruser.php @@ -1104,8 +1104,10 @@ if ($user->hasRight("holiday", "read")) { $event->type = 'holiday'; $event->type_picto = 'holiday'; - $event->datep = $db->jdate($obj->date_start) + (empty($obj->halfday) || $obj->halfday == 1 ? 0 : 12) * 60 * 60; - $event->datef = $db->jdate($obj->date_end) + (empty($obj->halfday) || $obj->halfday == -1 ? 24 : 12) * 60 * 60 - 1; + // date_debut and date_fin are dates without time, so they must be read and rendered in GMT to + // stay independent from the server and user timezones (otherwise the calendar day box is shifted). + $event->datep = $db->jdate($obj->date_start, 'gmt') + (empty($obj->halfday) || $obj->halfday == 1 ? 0 : 12) * 60 * 60; + $event->datef = $db->jdate($obj->date_end, 'gmt') + (empty($obj->halfday) || $obj->halfday == -1 ? 24 : 12) * 60 * 60 - 1; $event->date_start_in_calendar = $event->datep; $event->date_end_in_calendar = $event->datef; @@ -1124,14 +1126,14 @@ if ($user->hasRight("holiday", "read")) { $daycursor = $event->date_start_in_calendar; - $annee = (int) dol_print_date($daycursor, '%Y', 'tzuserrel'); - $mois = (int) dol_print_date($daycursor, '%m', 'tzuserrel'); - $jour = (int) dol_print_date($daycursor, '%d', 'tzuserrel'); + $annee = (int) dol_print_date($daycursor, '%Y', 'gmt'); + $mois = (int) dol_print_date($daycursor, '%m', 'gmt'); + $jour = (int) dol_print_date($daycursor, '%d', 'gmt'); $daycursorend = $event->date_end_in_calendar; - $anneeend = (int) dol_print_date($daycursorend, '%Y', 'tzuserrel'); - $moisend = (int) dol_print_date($daycursorend, '%m', 'tzuserrel'); - $jourend = (int) dol_print_date($daycursorend, '%d', 'tzuserrel'); + $anneeend = (int) dol_print_date($daycursorend, '%Y', 'gmt'); + $moisend = (int) dol_print_date($daycursorend, '%m', 'gmt'); + $jourend = (int) dol_print_date($daycursorend, '%d', 'gmt'); // daykey must be date that represent day box in calendar so must be a user time $daykey = dol_mktime(0, 0, 0, $mois, $jour, $annee, 'gmt'); From cc31d406389aa344a700825909aea1d644d0788d Mon Sep 17 00:00:00 2001 From: Maxime Kohlhaas Date: Thu, 13 Aug 2026 03:05:13 +0200 Subject: [PATCH 12/15] Fix : thirdparty birth was emptied on country change (#39440) --- htdocs/societe/card.php | 1 + 1 file changed, 1 insertion(+) diff --git a/htdocs/societe/card.php b/htdocs/societe/card.php index d1eb652bef2..aa2a4857757 100644 --- a/htdocs/societe/card.php +++ b/htdocs/societe/card.php @@ -1190,6 +1190,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($canvasdisplayactio $object->country = $tmparray['label']; } $object->forme_juridique_code = GETPOSTINT('forme_juridique_code'); + $object->birth = dol_mktime(0, 0, 0, GETPOSTINT('birthmonth'), GETPOSTINT('birthday'), GETPOSTINT('birthyear')); // We set multicurrency_code if enabled if (isModEnabled("multicurrency")) { From af6c24c0ce0ad13507ca9555d2ed08baa23faef2 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Thu, 13 Aug 2026 04:00:48 +0200 Subject: [PATCH 13/15] fix: remove bug in last PR 39407 merged (JS allowoverconsumption not delcared) (#39414) --- htdocs/mrp/js/lib_dispatch.js.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/mrp/js/lib_dispatch.js.php b/htdocs/mrp/js/lib_dispatch.js.php index b988d6be292..fc8c8ec714c 100644 --- a/htdocs/mrp/js/lib_dispatch.js.php +++ b/htdocs/mrp/js/lib_dispatch.js.php @@ -146,10 +146,10 @@ function addDispatchLine(index, type, mode) } if (error === 0) { - addDispatchTR(qtyOrdered, qtyDispatched, index, nbrTrs, warehouseId, inputId, type, '', mode, $row, allowoverconsumption); + addDispatchTR(qtyOrdered, qtyDispatched, index, nbrTrs, warehouseId, inputId, type, '', mode, $row); } } else { - addDispatchTR(qtyOrdered, qtyDispatched, index, nbrTrs, warehouseId, inputId, type, qty, mode, $row, allowoverconsumption); + addDispatchTR(qtyOrdered, qtyDispatched, index, nbrTrs, warehouseId, inputId, type, qty, mode, $row); } } From 7a82187acd26a2e732020ea4d7a4fa59bb3b006b Mon Sep 17 00:00:00 2001 From: w3houde Date: Thu, 13 Aug 2026 19:21:43 +0700 Subject: [PATCH 14/15] Fix #39245 set TRIGGER_PREFIX to BILL on Facture (#39497) On 23.0 the generic CommonObject methods (setPaymentMethods, setBankAccount, ...) build the trigger name from $this->TRIGGER_PREFIX, falling back to get_class() when it is empty. Facture does not declare it, so it fires FACTURE_MODIFY instead of the BILL_* events the class uses everywhere else (BILL_CREATE, BILL_MODIFY, ...). Modules listening on BILL_MODIFY miss those changes. Declare public $TRIGGER_PREFIX = 'BILL', like the other objects do (Holiday='HOLIDAY', Workstation='WORKSTATION', BOM='BOM'). Signed-off-by: Dolicraft Co-authored-by: Dolicraft --- htdocs/compta/facture/class/facture.class.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/htdocs/compta/facture/class/facture.class.php b/htdocs/compta/facture/class/facture.class.php index 79242f5e608..b051a687600 100644 --- a/htdocs/compta/facture/class/facture.class.php +++ b/htdocs/compta/facture/class/facture.class.php @@ -72,6 +72,11 @@ class Facture extends CommonInvoice */ public $element = 'facture'; + /** + * @var string Prefix used to build trigger event names in generic CommonObject methods (BILL_MODIFY, ...) + */ + public $TRIGGER_PREFIX = 'BILL'; + /** * @var string Name of table without prefix where object is stored */ From 5316cb76321dcfabd3fcf5e3b08c41865d0fc59d Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 13 Aug 2026 17:04:11 +0200 Subject: [PATCH 15/15] Fix subtotal when detail of line disabled (SUBTOTAL_HIDE_LINES_UNDER_TITLE) --- .../commande/doc/pdf_eratosthene.modules.php | 326 +++++++++-------- .../facture/doc/pdf_sponge.modules.php | 335 +++++++++--------- .../modules/propale/doc/pdf_cyan.modules.php | 4 +- 3 files changed, 352 insertions(+), 313 deletions(-) diff --git a/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php b/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php index 59d6b96e962..3fbf3f6865b 100644 --- a/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php +++ b/htdocs/core/modules/commande/doc/pdf_eratosthene.modules.php @@ -561,6 +561,8 @@ class pdf_eratosthene extends ModelePDFCommandes $pdf_sub_options['titleshowuponpdf'] = 1; $pdf_sub_options['titleshowtotalexludingvatonpdf'] = 1; + $hidenextline = 0; + for ($i = 0; $i < $nblines; $i++) { $linePosition = $i + 1; $curY = $nexY; @@ -570,6 +572,7 @@ class pdf_eratosthene extends ModelePDFCommandes if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE) { $level = $object->lines[$i]->qty; if ($sub_options) { + $hidenextline = 0; if (isset($sub_options['titleshowuponpdf'])) { $pdf_sub_options['titleshowuponpdf'] = isset($pdf_sub_options['titleshowuponpdf']) && $pdf_sub_options['titleshowuponpdf'] < $level ? $pdf_sub_options['titleshowuponpdf'] : $level; } elseif (isset($pdf_sub_options['titleshowuponpdf']) && abs($level) <= $pdf_sub_options['titleshowuponpdf']) { @@ -590,167 +593,178 @@ class pdf_eratosthene extends ModelePDFCommandes } } - if (($curY + 6) > ($this->page_hauteur - $heightforfooter) || isset($sub_options['titleforcepagebreak']) && !($pdf->getNumPages() == 1 && $curY == $tab_top + $this->tabTitleHeight)) { - $object->lines[$i]->pagebreak = true; + if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE && isset($sub_options['subtotalshowtotalexludingvatonpdf']) && getDolGlobalString('SUBTOTAL_HIDE_LINES_UNDER_TITLE')) { // TODO Use $sub_options['titlehidelinesundertitle'] instead of SUBTOTAL_HIDE_LINES_UNDER_TITLE + $hidenextline = 0; + $pdf_sub_options = array(); + $pdf_sub_options['titleshowuponpdf'] = 1; + $pdf_sub_options['titleshowtotalexludingvatonpdf'] = 1; } - // 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); + //var_dump($i, $sub_options, $hidenextline); + if ($hidenextline) { + $linePosition--; + } else { + if (($curY + 6) > ($this->page_hauteur - $heightforfooter) || isset($sub_options['titleforcepagebreak']) && !($pdf->getNumPages() == 1 && $curY == $tab_top + $this->tabTitleHeight)) { + $object->lines[$i]->pagebreak = true; } - $pdf->setPage($pdf->getNumPages()); - $nexY = $curY = $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('', true, $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); + // 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($pageposbefore + 1); - $pdf->setPageOrientation('', true, $heightforfooter); // The only function to edit the bottom margin of current page to set it. - $curY = $tab_top_newpage; - $showpricebeforepagebreak = 0; + + $pdf->setPage($pdf->getNumPages()); + $nexY = $curY = $tab_top_newpage; } - // I remove the line commented below because it probably uselesss (or bug source) no need to change bottom margin because we have checked image fit - //$pdf->setPageOrientation('', 0, $heightforfooter + $heightforfreetext); // The only function to edit the bottom margin of current page to set it. + $this->resetAfterColsLinePositionsData($nexY, $pdf->getPage()); - // @phan-suppress-next-line PhanTypeMismatchProperty - 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()); + + $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]); } - } - // restore Page orientation for text - $pdf->setPageOrientation('', true, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + $pdf->setTopMargin($tab_top_newpage); + $pdf->setPageOrientation('', true, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + $pageposbefore = $pdf->getPage(); + $curYBefore = $curY; - // Description of product line - if ($this->getColumnStatus('desc')) { - if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { - $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); - $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); - } else { - $bg_color = colorStringToArray(getDolGlobalString("SUBTOTAL_BACK_COLOR_LEVEL_".abs($object->lines[$i]->qty), 'ffffff')); - pdf_render_subtotals($pdf, $this, $curY, $object, $i, $outputlangs, $hideref, $hidedesc, $bg_color, true, true); + // 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('', true, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + $curY = $tab_top_newpage; + $showpricebeforepagebreak = 0; + } + + // I remove the line commented below because it probably uselesss (or bug source) no need to change bottom margin because we have checked image fit + //$pdf->setPageOrientation('', 0, $heightforfooter + $heightforfreetext); // The only function to edit the bottom margin of current page to set it. + + // @phan-suppress-next-line PhanTypeMismatchProperty + 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()); + } } - } - $afterPosData = $this->getMaxAfterColsLinePositionsData(); - $pdf->setPage($pageposbefore); - $pdf->setTopMargin($this->marge_haute); - $pdf->setPageOrientation('', false, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + // restore Page orientation for text + $pdf->setPageOrientation('', true, $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 - - // Line position - if ($this->getColumnStatus('position')) { - $this->printStdColumnContent($pdf, $curY, 'position', strval($linePosition)); - } - - // VAT Rate - if ($this->getColumnStatus('vat') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { - $vat_rate = pdf_getlinevatrate($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'vat', $vat_rate); - } - - // Unit price before discount - if ($this->getColumnStatus('subprice') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowuponpdf'])) { - $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') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { - $qty = pdf_getlineqty($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'qty', $qty); - } - - - // Unit - if ($this->getColumnStatus('unit') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { - $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 && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { - $remise_percent = pdf_getlineremisepercent($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'discount', $remise_percent); - } - - // Total excl tax line (HT) - if ($this->getColumnStatus('totalexcltax')) { - if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowtotalexludingvatonpdf'])) { - $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); - } elseif ($object->lines[$i]->qty < 0 && isset($sub_options['subtotalshowtotalexludingvatonpdf'])) { - if (isModEnabled('multicurrency') && $object->multicurrency_code != getDolCurrency()) { - $total_excl_tax = $object->getSubtotalLineMulticurrencyAmount($object->lines[$i]); + // Description of product line + if ($this->getColumnStatus('desc')) { + if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); + $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); } else { - $total_excl_tax = $object->getSubtotalLineAmount($object->lines[$i]); - } - $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()); + $bg_color = colorStringToArray(getDolGlobalString("SUBTOTAL_BACK_COLOR_LEVEL_".abs($object->lines[$i]->qty), 'ffffff')); + pdf_render_subtotals($pdf, $this, $curY, $object, $i, $outputlangs, $hideref, $hidedesc, $bg_color, true, true); } } - } - $afterPosData = $this->getMaxAfterColsLinePositionsData(); - $parameters = array( + $afterPosData = $this->getMaxAfterColsLinePositionsData(); + $pdf->setPage($pageposbefore); + $pdf->setTopMargin($this->marge_haute); + $pdf->setPageOrientation('', false, $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 + + // Line position + if ($this->getColumnStatus('position')) { + $this->printStdColumnContent($pdf, $curY, 'position', strval($linePosition)); + } + + // VAT Rate + if ($this->getColumnStatus('vat') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $vat_rate = pdf_getlinevatrate($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'vat', $vat_rate); + } + + // Unit price before discount + if ($this->getColumnStatus('subprice') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowuponpdf'])) { + $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') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $qty = pdf_getlineqty($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'qty', $qty); + } + + + // Unit + if ($this->getColumnStatus('unit') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $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 && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $remise_percent = pdf_getlineremisepercent($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'discount', $remise_percent); + } + + // Total excl tax line (HT) + if ($this->getColumnStatus('totalexcltax')) { + if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowtotalexludingvatonpdf'])) { + $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); + } elseif ($object->lines[$i]->qty < 0 && isset($sub_options['subtotalshowtotalexludingvatonpdf'])) { + if (isModEnabled('multicurrency') && $object->multicurrency_code != getDolCurrency()) { + $total_excl_tax = $object->getSubtotalLineMulticurrencyAmount($object->lines[$i]); + } else { + $total_excl_tax = $object->getSubtotalLineAmount($object->lines[$i]); + } + $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, @@ -758,9 +772,9 @@ class pdf_eratosthene extends ModelePDFCommandes '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 - + ); + $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) { @@ -817,16 +831,22 @@ class pdf_eratosthene extends ModelePDFCommandes } $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, $this->page_largeur - $this->marge_droite, $nexY); - $pdf->SetLineStyle(array('dash' => 0)); + if (!$hidenextline) { + $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, $this->page_largeur - $this->marge_droite, $nexY); + $pdf->SetLineStyle(array('dash' => 0)); + } + } + if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE && (isset($sub_options['titleshowuponpdf']) || isset($sub_options['titleshowtotalexludingvatonpdf'])) && getDolGlobalString('SUBTOTAL_HIDE_LINES_UNDER_TITLE')) { // TODO Use $sub_options['titlehidelinesundertitle'] instead of SUBTOTAL_HIDE_LINES_UNDER_TITLE + $hidenextline = 1; } $nexY += 0; // Add space between lines diff --git a/htdocs/core/modules/facture/doc/pdf_sponge.modules.php b/htdocs/core/modules/facture/doc/pdf_sponge.modules.php index b0872c0e8c3..c44af9d3d43 100644 --- a/htdocs/core/modules/facture/doc/pdf_sponge.modules.php +++ b/htdocs/core/modules/facture/doc/pdf_sponge.modules.php @@ -724,6 +724,8 @@ class pdf_sponge extends ModelePDFFactures $pdf_sub_options['titleshowuponpdf'] = 1; $pdf_sub_options['titleshowtotalexludingvatonpdf'] = 1; + $hidenextline = 0; + for ($i = 0; $i < $nblines; $i++) { $linePosition = $i + 1; $curY = $nexY; @@ -733,6 +735,7 @@ class pdf_sponge extends ModelePDFFactures if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE) { $level = $object->lines[$i]->qty; if ($sub_options) { + $hidenextline = 0; if (isset($sub_options['titleshowuponpdf'])) { $pdf_sub_options['titleshowuponpdf'] = isset($pdf_sub_options['titleshowuponpdf']) && $pdf_sub_options['titleshowuponpdf'] < $level ? $pdf_sub_options['titleshowuponpdf'] : $level; } elseif (isset($pdf_sub_options['titleshowuponpdf']) && abs($level) <= $pdf_sub_options['titleshowuponpdf']) { @@ -753,172 +756,182 @@ class pdf_sponge extends ModelePDFFactures } } - if (($curY + 6) > ($this->page_hauteur - $this->heightforfooter) || isset($sub_options['titleforcepagebreak']) && !($pdf->getNumPages() == 1 && $curY == $this->tab_top + $this->tabTitleHeight)) { - $object->lines[$i]->pagebreak = true; + if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE && isset($sub_options['subtotalshowtotalexludingvatonpdf']) && getDolGlobalString('SUBTOTAL_HIDE_LINES_UNDER_TITLE')) { // TODO Use $sub_options['titlehidelinesundertitle'] instead of SUBTOTAL_HIDE_LINES_UNDER_TITLE + $hidenextline = 0; + $pdf_sub_options = array(); + $pdf_sub_options['titleshowuponpdf'] = 1; + $pdf_sub_options['titleshowtotalexludingvatonpdf'] = 1; } - // 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); + if ($hidenextline) { + $linePosition--; + } else { + if (($curY + 6) > ($this->page_hauteur - $this->heightforfooter) || isset($sub_options['titleforcepagebreak']) && !($pdf->getNumPages() == 1 && $curY == $this->tab_top + $this->tabTitleHeight)) { + $object->lines[$i]->pagebreak = true; } - $pdf->setPage($pdf->getNumPages()); - $nexY = $curY = $this->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($this->tab_top_newpage); - $pdf->setPageOrientation('', true, $this->heightforfooter); - $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'); - - 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 - $this->heightforfooter)) { // If photo too high, we moved completely on new page - $pdf->AddPage('', '', true); + // 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($pageposbefore + 1); - $pdf->setPageOrientation('', true, $this->heightforfooter); // The only function to edit the bottom margin of current page to set it. - $curY = $this->tab_top_newpage; - $showpricebeforepagebreak = 0; + + $pdf->setPage($pdf->getNumPages()); + $nexY = $curY = $this->tab_top_newpage; } - $pdf->setPageOrientation('', false, $this->heightforfooter + $this->heightforfreetext); // The only function to edit the bottom margin of current page to set it. - // @phan-suppress-next-line PhanTypeMismatchProperty - 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->resetAfterColsLinePositionsData($nexY, $pdf->getPage()); - $this->setAfterColsLinePositionsData('photo', $posYAfterImage, $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]); } - } - // restore Page orientation for text - $pdf->setPageOrientation('', true, $this->heightforfooter); // The only function to edit the bottom margin of current page to set it. + $pdf->setTopMargin($this->tab_top_newpage); + $pdf->setPageOrientation('', true, $this->heightforfooter); + $pageposbefore = $pdf->getPage(); + $curYBefore = $curY; - // Description of product line - if ($this->getColumnStatus('desc')) { - if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { - $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); - $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); - } else { - $bg_color = colorStringToArray(getDolGlobalString("SUBTOTAL_BACK_COLOR_LEVEL_".abs($object->lines[$i]->qty), 'ffffff')); - pdf_render_subtotals($pdf, $this, $curY, $object, $i, $outputlangs, $hideref, $hidedesc, $bg_color, true, true); + // Allows data in the first page if description is long enough to break in multiples pages + $showpricebeforepagebreak = getDolGlobalInt('MAIN_PDF_DATA_ON_FIRST_PAGE'); + + 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 - $this->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('', true, $this->heightforfooter); // The only function to edit the bottom margin of current page to set it. + $curY = $this->tab_top_newpage; + $showpricebeforepagebreak = 0; + } + + $pdf->setPageOrientation('', false, $this->heightforfooter + $this->heightforfreetext); // The only function to edit the bottom margin of current page to set it. + // @phan-suppress-next-line PhanTypeMismatchProperty + 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('', true, $this->heightforfooter); // The only function to edit the bottom margin of current page to set it. - $afterPosData = $this->getMaxAfterColsLinePositionsData(); - $pdf->setPage($pageposbefore); - $pdf->setTopMargin($this->marge_haute); - $curY = $curYBefore; - $pdf->setPageOrientation('', false, $this->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 - $this->heightforfooter))) { - $pdf->setPage($afterPosData['page']); - $curY = $this->tab_top_newpage; - } - - $pdf->SetFont('', '', $default_font_size - 1); // We reposition the default font - - // Line position - if ($this->getColumnStatus('position')) { - $this->printStdColumnContent($pdf, $curY, 'position', strval($linePosition)); - } - - // VAT Rate - if ($this->getColumnStatus('vat') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { - $vat_rate = pdf_getlinevatrate($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'vat', $vat_rate); - } - - // Unit price before discount - if ($this->getColumnStatus('subprice') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowuponpdf'])) { - $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') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { - $qty = pdf_getlineqty($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'qty', $qty); - } - - // Situation progress - if ($this->getColumnStatus('progress') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { - $progress = pdf_getlineprogress($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'progress', $progress); - } - - // Unit - if ($this->getColumnStatus('unit') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { - $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 && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { - $remise_percent = pdf_getlineremisepercent($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'discount', $remise_percent); - } - - // Total excl tax line (HT) - if ($this->getColumnStatus('totalexcltax')) { - if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowtotalexludingvatonpdf'])) { - $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); - $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); - } elseif ($object->lines[$i]->qty < 0 && isset($sub_options['subtotalshowtotalexludingvatonpdf'])) { - if (isModEnabled('multicurrency') && $object->multicurrency_code != $conf->currency) { - $total_excl_tax = $object->getSubtotalLineMulticurrencyAmount($object->lines[$i]); + // Description of product line + if ($this->getColumnStatus('desc')) { + if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $this->printColDescContent($pdf, $curY, 'desc', $object, $i, $outputlangs, $hideref, $hidedesc); + $this->setAfterColsLinePositionsData('desc', $pdf->GetY(), $pdf->getPage()); } else { - $total_excl_tax = $object->getSubtotalLineAmount($object->lines[$i]); - } - $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()); + $bg_color = colorStringToArray(getDolGlobalString("SUBTOTAL_BACK_COLOR_LEVEL_".abs($object->lines[$i]->qty), 'ffffff')); + pdf_render_subtotals($pdf, $this, $curY, $object, $i, $outputlangs, $hideref, $hidedesc, $bg_color, true, true); } } - } - $afterPosData = $this->getMaxAfterColsLinePositionsData(); - $parameters = array( + + $afterPosData = $this->getMaxAfterColsLinePositionsData(); + $pdf->setPage($pageposbefore); + $pdf->setTopMargin($this->marge_haute); + $curY = $curYBefore; + $pdf->setPageOrientation('', false, $this->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 - $this->heightforfooter))) { + $pdf->setPage($afterPosData['page']); + $curY = $this->tab_top_newpage; + } + + $pdf->SetFont('', '', $default_font_size - 1); // We reposition the default font + + // Line position + if ($this->getColumnStatus('position')) { + $this->printStdColumnContent($pdf, $curY, 'position', strval($linePosition)); + } + + // VAT Rate + if ($this->getColumnStatus('vat') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $vat_rate = pdf_getlinevatrate($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'vat', $vat_rate); + } + + // Unit price before discount + if ($this->getColumnStatus('subprice') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowuponpdf'])) { + $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') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $qty = pdf_getlineqty($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'qty', $qty); + } + + // Situation progress + if ($this->getColumnStatus('progress') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $progress = pdf_getlineprogress($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'progress', $progress); + } + + // Unit + if ($this->getColumnStatus('unit') && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $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 && $object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE) { + $remise_percent = pdf_getlineremisepercent($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'discount', $remise_percent); + } + + // Total excl tax line (HT) + if ($this->getColumnStatus('totalexcltax')) { + if ($object->lines[$i]->special_code != SUBTOTALS_SPECIAL_CODE && isset($pdf_sub_options['titleshowtotalexludingvatonpdf'])) { + $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); + $this->printStdColumnContent($pdf, $curY, 'totalexcltax', $total_excl_tax); + } elseif ($object->lines[$i]->qty < 0 && isset($sub_options['subtotalshowtotalexludingvatonpdf'])) { + if (isModEnabled('multicurrency') && $object->multicurrency_code != $conf->currency) { + $total_excl_tax = $object->getSubtotalLineMulticurrencyAmount($object->lines[$i]); + } else { + $total_excl_tax = $object->getSubtotalLineAmount($object->lines[$i]); + } + $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, @@ -926,9 +939,9 @@ class pdf_sponge extends ModelePDFFactures '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 - + ); + $reshook = $hookmanager->executeHooks('printPDFline', $parameters, $this); // Note that $object may have been modified by hook + } $sign = 1; if (isset($object->type) && $object->type == 2 && getDolGlobalString('INVOICE_POSITIVE_CREDIT_NOTE')) { @@ -1006,16 +1019,22 @@ class pdf_sponge extends ModelePDFFactures $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']; + if (!$hidenextline) { + $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 - $this->heightforfooter - 5) { - $pdf->SetLineStyle(array('dash' => '1,1', 'color' => array(80, 80, 80))); - //$pdf->SetDrawColor(190,190,200); - $pdf->line($this->marge_gauche, $nexY, $this->page_largeur - $this->marge_droite, $nexY); - $pdf->SetLineStyle(array('dash' => 0)); + // Add line + if (getDolGlobalString('MAIN_PDF_DASH_BETWEEN_LINES') && $i < ($nblines - 1) && $afterPosData['y'] < $this->page_hauteur - $this->heightforfooter - 5) { + $pdf->SetLineStyle(array('dash' => '1,1', 'color' => array(80, 80, 80))); + //$pdf->SetDrawColor(190,190,200); + $pdf->line($this->marge_gauche, $nexY, $this->page_largeur - $this->marge_droite, $nexY); + $pdf->SetLineStyle(array('dash' => 0)); + } + } + + if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE && (isset($sub_options['titleshowuponpdf']) || isset($sub_options['titleshowtotalexludingvatonpdf'])) && getDolGlobalString('SUBTOTAL_HIDE_LINES_UNDER_TITLE')) { // TODO Use $sub_options['titlehidelinesundertitle'] instead of SUBTOTAL_HIDE_LINES_UNDER_TITLE + $hidenextline = 1; } $nexY += 0; // Add space between lines diff --git a/htdocs/core/modules/propale/doc/pdf_cyan.modules.php b/htdocs/core/modules/propale/doc/pdf_cyan.modules.php index 4fc395a4696..63a5f3fd568 100644 --- a/htdocs/core/modules/propale/doc/pdf_cyan.modules.php +++ b/htdocs/core/modules/propale/doc/pdf_cyan.modules.php @@ -575,10 +575,10 @@ class pdf_cyan extends ModelePDFPropales $sub_options = $object->lines[$i]->extraparams["subtotal"] ?? array(); - if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE) { $level = $object->lines[$i]->qty; if ($sub_options) { + $hidenextline = 0; if (isset($sub_options['titleshowuponpdf'])) { $pdf_sub_options['titleshowuponpdf'] = isset($pdf_sub_options['titleshowuponpdf']) && $pdf_sub_options['titleshowuponpdf'] < $level ? $pdf_sub_options['titleshowuponpdf'] : $level; } elseif (isset($pdf_sub_options['titleshowuponpdf']) && abs($level) <= $pdf_sub_options['titleshowuponpdf']) { @@ -864,7 +864,7 @@ class pdf_cyan extends ModelePDFPropales } } - if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE && isset($sub_options['titleshowuponpdf']) && getDolGlobalString('SUBTOTAL_HIDE_LINES_UNDER_TITLE')) { // TODO Use $sub_options['titlehidelinesundertitle'] instead of SUBTOTAL_HIDE_LINES_UNDER_TITLE + if ($object->lines[$i]->special_code == SUBTOTALS_SPECIAL_CODE && (isset($sub_options['titleshowuponpdf']) || isset($sub_options['titleshowtotalexludingvatonpdf'])) && getDolGlobalString('SUBTOTAL_HIDE_LINES_UNDER_TITLE')) { // TODO Use $sub_options['titlehidelinesundertitle'] instead of SUBTOTAL_HIDE_LINES_UNDER_TITLE $hidenextline = 1; }