From 58e0837a7afead8e2a125c84d94b662973350679 Mon Sep 17 00:00:00 2001 From: Bradley Jarvis Date: Tue, 9 Apr 2024 10:37:30 +1000 Subject: [PATCH 1/5] FIX Add CSV download link to stock at date table (#29016) * FIX Add CSV download link to stock at date table * Update array values to strings for joining * Fix array and string formatting * Fix space after comma * FIX stock at date csv download Removed .htaccess and requirement for modrewrite Added output url parameter which takes csv as argument updated GETPOST(,'int') calls to GETPOSTINT() * FIX stock at date csv download Removed .htaccess and requirement for modrewrite Added output url parameter which takes csv as argument updated GETPOST(,'int') calls to GETPOSTINT() * FIX removed extra 'int' parameter from GETPOSTINT() * Fix wrapped stock values in price(price2num($stock,'MS')) * Fix error in php code * Fix missed wrapping some stock levels tin price(...) * Update stockatdate.php --------- Co-authored-by: brad Co-authored-by: Laurent Destailleur --- htdocs/product/stock/stockatdate.php | 527 +++++++++++++++------------ 1 file changed, 300 insertions(+), 227 deletions(-) diff --git a/htdocs/product/stock/stockatdate.php b/htdocs/product/stock/stockatdate.php index 6b39de6629547..1cc98b5648726 100644 --- a/htdocs/product/stock/stockatdate.php +++ b/htdocs/product/stock/stockatdate.php @@ -54,6 +54,8 @@ $type = GETPOSTINT('type'); $mode = GETPOST('mode', 'alpha'); +$ext=(GETPOSTISSET('output') && in_array(GETPOST('output'), array('csv'))) ? GETPOST('output') : ''; + $date = ''; $dateendofday = ''; if (GETPOSTISSET('dateday') && GETPOSTISSET('datemonth') && GETPOSTISSET('dateyear')) { @@ -245,8 +247,8 @@ $movements_prod_warehouse_nb[$fk_product][$fk_entrepot] = $nbofmovement; // Pour llx_product.stock - $movements_prod[$fk_product] += $stock; - $movements_prod_nb[$fk_product] += $nbofmovement; + $movements_prod[$fk_product] = $stock + (array_key_exists($fk_product, $movements_prod)?$movements_prod[$fk_product]:0); + $movements_prod_nb[$fk_product] = $nbofmovement + (array_key_exists($fk_product, $movements_prod_nb)?$movements_prod_nb[$fk_product]:0); $i++; } @@ -330,16 +332,20 @@ if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { $result = $db->query($sql); $nbtotalofrecords = $db->num_rows($result); - if (($page * $limit) > $nbtotalofrecords) { // if total resultset is smaller then paging size (filtering), goto and load page 0 + if (($page * $limit) > $nbtotalofrecords || $ext == 'csv') { // if total resultset is smaller then paging size (filtering), goto and load page 0 $page = 0; $offset = 0; } } - $sql .= $db->plimit($limit + 1, $offset); - //print $sql; - $resql = $db->query($sql); + if ($ext != 'csv') { + $sql .= $db->plimit($limit + 1, $offset); + $resql = $db->query($sql); + } else { + $resql = $result; + $limit = 0; + } if (empty($resql)) { dol_print_error($db); exit; @@ -353,164 +359,188 @@ $helpurl = 'EN:Module_Stocks_En|FR:Module_Stock|'; $helpurl .= 'ES:Módulo_Stocks'; -llxHeader('', $title, $helpurl, ''); +$stocklabel = $langs->trans('StockAtDate'); +if ($mode == 'future') { + $stocklabel = $langs->trans("VirtualStockAtDate"); +} -$head = array(); +// TODO Move this action into a separated files: We should not mix output with MIME type HTML and MIME type CSV in the same file. +if ($ext == 'csv') { + header("Content-Type: text/csv"); + header("Content-Disposition: attachment; filename=stock".($date?'-'.date("Y-m-d", $date):'').".csv"); -$head[0][0] = DOL_URL_ROOT.'/product/stock/stockatdate.php'; -$head[0][1] = $langs->trans("StockAtDateInPast"); -$head[0][2] = 'stockatdatepast'; + // Lines of title + print implode("\t", ($mode == 'future')? + array('"Product Reference"', '"Label"', '"Current Stock"', '"'.$stocklabel.'"', '"Virtual Stock"'): + array('"Product Reference"', '"Label"', '"'.$stocklabel.'"', '"Estimated Stock Value"', '"Estimate Sell Value"', '"Movements"', '"Current Stock"'))."\r\n"; +} else { + llxHeader('', $title, $helpurl, ''); -$head[1][0] = DOL_URL_ROOT.'/product/stock/stockatdate.php?mode=future'; -$head[1][1] = $langs->trans("StockAtDateInFuture"); -$head[1][2] = 'stockatdatefuture'; + $head = array(); + $head[0][0] = DOL_URL_ROOT.'/product/stock/stockatdate.php'; + $head[0][1] = $langs->trans("StockAtDateInPast"); + $head[0][2] = 'stockatdatepast'; -print load_fiche_titre($langs->trans('StockAtDate'), '', 'stock'); + $head[1][0] = DOL_URL_ROOT.'/product/stock/stockatdate.php?mode=future'; + $head[1][1] = $langs->trans("StockAtDateInFuture"); + $head[1][2] = 'stockatdatefuture'; -print dol_get_fiche_head($head, ($mode == 'future' ? 'stockatdatefuture' : 'stockatdatepast'), '', -1, ''); -$desc = $langs->trans("StockAtDatePastDesc"); -if ($mode == 'future') { - $desc = $langs->trans("StockAtDateFutureDesc"); -} -print ''.$desc.'
'."\n"; -print '
'."\n"; + print load_fiche_titre($langs->trans('StockAtDate'), '', 'stock'); -print '
'; -print ''; -print ''; -print ''; + print dol_get_fiche_head($head, ($mode == 'future' ? 'stockatdatefuture' : 'stockatdatepast'), '', -1, ''); + + $desc = $langs->trans("StockAtDatePastDesc"); + if ($mode == 'future') { + $desc = $langs->trans("StockAtDateFutureDesc"); + } + print ''.$desc.'
'."\n"; + print '
'."\n"; -print '
'; -print ''.$langs->trans('Date').' '.$form->selectDate(($date ? $date : -1), 'date'); + print ''; + print ''; + print ''; + print ''; -print '   '; -print img_picto('', 'product', 'class="pictofiwedwidth"').' '; -print ' '; -print $form->select_produits($productid, 'productid', '', 0, 0, -1, 2, '', 0, array(), 0, $langs->trans('Product'), 0, 'maxwidth300', 0, '', null, 1); + print '
'; + print ''.$langs->trans('Date').' '.$form->selectDate(($date ? $date : -1), 'date'); -if ($mode != 'future') { - // A virtual stock in future has no sense on a per warehouse view, so no filter on warehouse is available for stock at date in future print '   '; - print img_picto('', 'stock', 'class="pictofixedwidth"').$langs->trans("Warehouse").' :'; + print img_picto('', 'product', 'class="pictofiwedwidth"').' '; print ' '; - $selected = ((GETPOSTISSET('search_fk_warehouse') || GETPOSTISSET('fk_warehouse')) ? $search_fk_warehouse : 'ifonenodefault'); - print $formproduct->selectWarehouses($selected, 'search_fk_warehouse', '', 1, 0, 0, $langs->trans('Warehouse'), 0, 0, null, 'minwidth200', null, 1, false, 'e.ref', 1); -} - -print '
'; - -$parameters = array(); -$reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters); // Note that $action and $object may have been modified by hook -if (empty($reshook)) { - print $hookmanager->resPrint; -} - -print '
'; -print ''; -print '
'; + print $form->select_produits($productid, 'productid', '', 0, 0, -1, 2, '', 0, array(), 0, $langs->trans('Product'), 0, 'maxwidth300', 0, '', null, 1); + + if ($mode != 'future') { + // A virtual stock in future has no sense on a per warehouse view, so no filter on warehouse is available for stock at date in future + print '   '; + print img_picto('', 'stock', 'class="pictofixedwidth"').$langs->trans("Warehouse").' :'; + print ' '; + $selected = ((GETPOSTISSET('search_fk_warehouse') || GETPOSTISSET('fk_warehouse')) ? $search_fk_warehouse : 'ifonenodefault'); + print $formproduct->selectWarehouses($selected, 'search_fk_warehouse', '', 1, 0, 0, $langs->trans('Warehouse'), 0, 0, null, 'minwidth200', null, 1, false, 'e.ref', 1); + } -//print ''; + print '
'; -$param = ''; -if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) { - $param .= '&contextpage='.urlencode($contextpage); -} -if ($limit > 0 && $limit != $conf->liste_limit) { - $param .= '&limit='.((int) $limit); -} -$param .= '&mode='.$mode; -if (!empty($search_fk_warehouse)) { - foreach ($search_fk_warehouse as $val) { - $param .= '&search_fk_warehouse[]='.$val; + $parameters = array(); + $reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters); // Note that $action and $object may have been modified by hook + if (empty($reshook)) { + print $hookmanager->resPrint; } -} -if ($productid > 0) { - $param .= '&productid='.(int) $productid; -} -if (GETPOSTINT('dateday') > 0) { - $param .= '&dateday='.GETPOSTINT('dateday'); -} -if (GETPOSTINT('datemonth') > 0) { - $param .= '&datemonth='.GETPOSTINT('datemonth'); -} -if (GETPOSTINT('dateyear') > 0) { - $param .= '&dateyear='.GETPOSTINT('dateyear'); -} -// TODO Move this into the title line ? -print_barre_liste('', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $nbtotalofrecords, 'stock', 0, '', '', $limit, 0, 0, 1); + print '
'; + print ''; + print '
'; -print '
'; // You can use div-table-responsive-no-min if you don't need reserved height for your table -print ''; + //print ''; -$stocklabel = $langs->trans('StockAtDate'); -if ($mode == 'future') { - $stocklabel = $langs->trans("VirtualStockAtDate"); -} + $param = ''; + if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) { + $param .= '&contextpage='.urlencode($contextpage); + } + if ($limit > 0 && $limit != $conf->liste_limit) { + $param .= '&limit='.((int) $limit); + } + $param .= '&mode='.$mode; + $param_warehouse = ''; + if (!empty($search_fk_warehouse)) { + foreach ($search_fk_warehouse as $val) { + $param_warehouse .= '&search_fk_warehouse[]='.$val; + } + $param .= $param_warehouse; + } + if ($productid > 0) { + $param .= '&productid='.(int) $productid; + } + if (GETPOSTINT('dateday') > 0) { + $param .= '&dateday='.GETPOSTINT('dateday'); + } + if (GETPOSTINT('datemonth') > 0) { + $param .= '&datemonth='.GETPOSTINT('datemonth'); + } + if (GETPOSTINT('dateyear') > 0) { + $param .= '&dateyear='.GETPOSTINT('dateyear'); + } -print ''; -print ''; -print ''; -print ''; -print ''; - -// Fields title search -print ''; -print ''; -print ''; -print ''; -print ''; -print ''; -if ($mode == 'future') { + // TODO Move this into the title line ? + print_barre_liste('', $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $nbtotalofrecords, 'stock', 0, '', '', $limit, 0, 0, 1); + + print '
'; // You can use div-table-responsive-no-min if you don't need reserved height for your table + if ($num) { + print "

Download CSV

"; + } + print '
'; + + print ''; + print ''; + print ''; + print ''; + print ''; + + // Fields title search + print ''; + print ''; + print ''; print ''; -} else { print ''; print ''; -} -// Fields from hook -$parameters = array('param'=>$param, 'sortfield'=>$sortfield, 'sortorder'=>$sortorder); -$reshook = $hookmanager->executeHooks('printFieldListOption', $parameters); // Note that $action and $object may have been modified by hook -print $hookmanager->resPrint; - -print ''; -print ''; - -$fieldtosortcurrentstock = 'stock'; -if (!empty($search_fk_warehouse)) { - $fieldtosortcurrentstock = 'stock_reel'; -} + if ($mode == 'future') { + print ''; + } else { + print ''; + print ''; + } + // Fields from hook + $parameters = array('param'=>$param, 'sortfield'=>$sortfield, 'sortorder'=>$sortorder); + $reshook = $hookmanager->executeHooks('printFieldListOption', $parameters); // Note that $action and $object may have been modified by hook + print $hookmanager->resPrint; -// Lines of title -print ''; -print_liste_field_titre('ProductRef', $_SERVER["PHP_SELF"], 'p.ref', $param, '', '', $sortfield, $sortorder); -print_liste_field_titre('Label', $_SERVER["PHP_SELF"], 'p.label', $param, '', '', $sortfield, $sortorder); + print ''; + print ''; -if ($mode == 'future') { - print_liste_field_titre('CurrentStock', $_SERVER["PHP_SELF"], $fieldtosortcurrentstock, $param, '', '', $sortfield, $sortorder, 'right '); - print_liste_field_titre('', $_SERVER["PHP_SELF"]); - print_liste_field_titre($stocklabel, $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right ', 'VirtualStockAtDateDesc'); - print_liste_field_titre('VirtualStock', $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right ', 'VirtualStockDesc'); -} else { - print_liste_field_titre($stocklabel, $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right '); - print_liste_field_titre("EstimatedStockValue", $_SERVER["PHP_SELF"], "estimatedvalue", '', $param, '', $sortfield, $sortorder, 'right ', $langs->trans("AtDate"), 1); - print_liste_field_titre("EstimatedStockValueSell", $_SERVER["PHP_SELF"], "", '', $param, '', $sortfield, $sortorder, 'right ', $langs->trans("AtDate"), 1); - print_liste_field_titre('', $_SERVER["PHP_SELF"]); - print_liste_field_titre('CurrentStock', $_SERVER["PHP_SELF"], $fieldtosortcurrentstock, $param, '', '', $sortfield, $sortorder, 'right '); -} + $fieldtosortcurrentstock = 'stock'; + if (!empty($search_fk_warehouse)) { + $fieldtosortcurrentstock = 'stock_reel'; + } + + // Lines of title + print ''; + print_liste_field_titre('ProductRef', $_SERVER["PHP_SELF"], 'p.ref', $param, '', '', $sortfield, $sortorder); + print_liste_field_titre('Label', $_SERVER["PHP_SELF"], 'p.label', $param, '', '', $sortfield, $sortorder); -// Hook fields -$parameters = array('param'=>$param, 'sortfield'=>$sortfield, 'sortorder'=>$sortorder); -$reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters); // Note that $action and $object may have been modified by hook -print $hookmanager->resPrint; + if ($mode == 'future') { + print_liste_field_titre('CurrentStock', $_SERVER["PHP_SELF"], $fieldtosortcurrentstock, $param, '', '', $sortfield, $sortorder, 'right '); + print_liste_field_titre('', $_SERVER["PHP_SELF"]); + print_liste_field_titre($stocklabel, $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right ', 'VirtualStockAtDateDesc'); + print_liste_field_titre('VirtualStock', $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right ', 'VirtualStockDesc'); + } else { + print_liste_field_titre($stocklabel, $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right '); + print_liste_field_titre("EstimatedStockValue", $_SERVER["PHP_SELF"], "estimatedvalue", '', $param, '', $sortfield, $sortorder, 'right ', $langs->trans("AtDate"), 1); + print_liste_field_titre("EstimatedStockValueSell", $_SERVER["PHP_SELF"], "", '', $param, '', $sortfield, $sortorder, 'right ', $langs->trans("AtDate"), 1); + print_liste_field_titre('', $_SERVER["PHP_SELF"]); + print_liste_field_titre('CurrentStock', $_SERVER["PHP_SELF"], $fieldtosortcurrentstock, $param, '', '', $sortfield, $sortorder, 'right '); + } -print_liste_field_titre('', $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right '); + // Hook fields + $parameters = array('param'=>$param, 'sortfield'=>$sortfield, 'sortorder'=>$sortorder); + $reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters); // Note that $action and $object may have been modified by hook + print $hookmanager->resPrint; -print "\n"; + print_liste_field_titre('', $_SERVER["PHP_SELF"], '', $param, '', '', $sortfield, $sortorder, 'right '); + + print "\n"; +} $totalbuyingprice = 0; $totalsellingprice = 0; @@ -582,135 +612,178 @@ } - print ''; + if ($ext == 'csv') { + if ($mode == 'future') { + print implode("\t", array( + '"'.$objp->ref.'"', + '"'.$objp->label.'"', + '"'.price(price2num($currentstock, 'MS')).'"', + '"'.price(price2num($stock, 'MS')).'"', + '"'.price(price2num($virtualstock, 'MS')).'"'))."\r\n"; + $totalvirtualstock += $virtualstock; + } else { + print implode("\t", array( + '"'.$objp->ref.'"', + '"'.$objp->label.'"', + '"'.price(price2num($stock, 'MS')).'"', + price2num($stock * $objp->pmp, 'MT')?'"'.price(price2num($stock * $objp->pmp, 'MT'), 1).'"':'', + empty($conf->global->PRODUIT_MULTIPRICES)?'"'.price(price2num($stock * $objp->price, 'MT'), 1).'"':'"'.$langs->trans("Variable").'('.$langs->trans("OptionMULTIPRICESIsOn").')"', + "$nbofmovement", + '"'.price(price2num($currentstock, 'MS')).'"'))."\r\n"; + $totalbuyingprice += $stock * $objp->pmp; + $totalsellingprice += $stock * $objp->price; + } + $totalcurrentstock += $currentstock; + } else { + print ''; - // Product ref - print ''; + // Product ref + print ''; - // Product label - print ''; + // Product label + print ''; - if ($mode == 'future') { - // Current stock - print ''; - $totalcurrentstock += $currentstock; + if ($mode == 'future') { + // Current stock + print ''; + //$totalcurrentstock += $currentstock; - print ''; + print ''; - // Virtual stock at date - print ''; + // Virtual stock at date + print ''; - // Final virtual stock - print ''; - $totalvirtualstock += $virtualstock; - } else { - // Stock at date - print ''; - - // PMP value - print ''; + $totalvirtualstock += $virtualstock; } else { - print ''; - } - $totalbuyingprice += $estimatedvalue; - print ''; - - // Selling value - print ''; + + // PMP value + print ''; - - print ''; + print ''; + + print ''; - // Current stock - print ''; + // Current stock + print ''; + } $totalcurrentstock += $currentstock; - } - // Fields from hook - $parameters = array('objp'=>$objp); - $reshook = $hookmanager->executeHooks('printFieldListValue', $parameters); // Note that $action and $object may have been modified by hook - print $hookmanager->resPrint; + // Fields from hook + $parameters = array('objp'=>$objp); + $reshook = $hookmanager->executeHooks('printFieldListValue', $parameters); // Note that $action and $object may have been modified by hook + print $hookmanager->resPrint; - // Action - print ''; + // Action + print ''; - print ''."\n"; + print ''."\n"; + } } $i++; } $parameters = array('sql'=>$sql); $reshook = $hookmanager->executeHooks('printFieldListFooter', $parameters); // Note that $action and $object may have been modified by hook -print $hookmanager->resPrint; +if ($ext!='csv') { + print $hookmanager->resPrint; +} $colspan = 8; if ($mode == 'future') { $colspan++; } - -if (empty($date) || !$dateIsValid) { - print ''; +if ($ext=='csv') { + print implode("\t", + ($mode == 'future')?array( + '"'.$langs->trans("Totalforthispage").'"', + '', + $productid > 0 ? price(price2num($totalcurrentstock, 'MS')) : '', + '', + price(price2num($totalvirtualstock, 'MS'))): + array( + '"'.$langs->trans("Totalforthispage").'"', + '', + '', + '"'.price(price2num($totalbuyingprice, 'MT')).'"', + !getDolGlobalString('PRODUIT_MULTIPRICES')?'"'.price(price2num($totalsellingprice, 'MT')).'"':'', + '', + $productid > 0 ? price(price2num($totalcurrentstock, 'MS')) : '')); } else { - print ''; - print ''; - print ''; - if ($mode == 'future') { - print ''; - print ''; - print ''; - print ''; + if (empty($date) || !$dateIsValid) { + print ''; } else { + print ''; + print ''; print ''; - print ''; - if (!getDolGlobalString('PRODUIT_MULTIPRICES')) { - print ''; + if ($mode == 'future') { + print ''; + print ''; + print ''; + print ''; } else { print ''; + print ''; + if (!getDolGlobalString('PRODUIT_MULTIPRICES')) { + print ''; + } else { + print ''; + } + print ''; + print ''; } print ''; - print ''; + print ''; } - print ''; - print ''; -} -print '
'; -$searchpicto = $form->showFilterAndCheckAddButtons(0); -print $searchpicto; -print '
'; + $searchpicto = $form->showFilterAndCheckAddButtons(0); + print $searchpicto; + print '
'.$prod->getNomUrl(1, '').''.$prod->getNomUrl(1, '').''; - print dol_escape_htmltag($objp->label); - print ''; + print dol_escape_htmltag($objp->label); + print ''.$currentstock.''.price(price2num($currentstock, 'MS')).''.$stock.''.price(price2num($stock, 'MS')).''.$virtualstock.''.($stock ? $stock : ''.$stock.'').''; - $estimatedvalue = $stock * $objp->pmp; - if (price2num($estimatedvalue, 'MT')) { - print ''.price(price2num($estimatedvalue, 'MT'), 1).''; + // Final virtual stock + print ''.price(price2num($virtualstock, 'MS')).''; - if (!getDolGlobalString('PRODUIT_MULTIPRICES')) { - print ''; - if ($stock || (float) ($stock * $objp->price)) { - print price(price2num($stock * $objp->price, 'MT'), 1); + // Stock at date + print ''.($stock ? price(price2num($stock, 'MS')) : ('0')).''; + $estimatedvalue = $stock * $objp->pmp; + if (price2num($estimatedvalue, 'MT')) { + print ''.price(price2num($estimatedvalue, 'MT'), 1).''; + } else { + print ''; } - print ''; - $totalsellingprice += $stock * $objp->price; - } else { - $htmltext = $langs->trans("OptionMULTIPRICESIsOn"); - print $form->textwithtooltip(''.$langs->trans("Variable").'', $htmltext); - } - print ''; - if ($nbofmovement > 0) { - print ''; + if (!getDolGlobalString('PRODUIT_MULTIPRICES')) { + print ''; + if ($stock || (float) ($stock * $objp->price)) { + print price(price2num($stock * $objp->price, 'MT'), 1); + } + print ''; + $totalsellingprice += $stock * $objp->price; + } else { + $htmltext = $langs->trans("OptionMULTIPRICESIsOn"); + print $form->textwithtooltip(''.$langs->trans("Variable").'', $htmltext); } - print '">'.$langs->trans("Movements").''; - print ' '.$nbofmovement.''; - } - print ''; + if ($nbofmovement > 0) { + print ''.$langs->trans("Movements").''; + print ' '.$nbofmovement.''; + } + print ''.($currentstock ? $currentstock : '0').''.($currentstock ? price(price2num($currentstock, 'MS')) : '0').'
'.$langs->trans("EnterADateCriteria").'
'.$langs->trans("Totalforthispage").''.price(price2num($totalcurrentstock, 'MS')).''.price(price2num($totalvirtualstock, 'MS')).'
'.$langs->trans("EnterADateCriteria").'
'.$langs->trans("Totalforthispage").''.price(price2num($totalbuyingprice, 'MT')).''.price(price2num($totalsellingprice, 'MT')).''.price(price2num($totalcurrentstock, 'MS')).''.price(price2num($totalvirtualstock, 'MS')).''.price(price2num($totalbuyingprice, 'MT')).''.price(price2num($totalsellingprice, 'MT')).''.($productid > 0 ? price(price2num($totalcurrentstock, 'MS')) : '').''.($productid > 0 ? price(price2num($totalcurrentstock, 'MS')) : '').'
'; -print '
'; + print ''; + print ''; -if (!empty($resql)) { - $db->free($resql); -} + print dol_get_fiche_end(); -print dol_get_fiche_end(); + print ''; -print ''; + llxFooter(); +} -llxFooter(); +if (!empty($resql)) { + $db->free($resql); +} $db->close(); From 4880ec1b771f3d264f9b922d69bdbf0d2061fe98 Mon Sep 17 00:00:00 2001 From: jyhere Date: Tue, 9 Apr 2024 03:09:10 +0200 Subject: [PATCH 2/5] NEW: Hooks tab in debugbar (#24992) Co-authored-by: Laurent Destailleur --- htdocs/core/class/hookmanager.class.php | 25 +++++++ .../class/DataCollector/DolHooksCollector.php | 73 +++++++++++++++++++ .../class/DataCollector/DolibarrCollector.php | 3 +- htdocs/debugbar/class/DebugBar.php | 2 + htdocs/debugbar/css/widgets.css | 45 ++++++++++++ htdocs/debugbar/js/widgets.js | 27 ++++++- 6 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 htdocs/debugbar/class/DataCollector/DolHooksCollector.php create mode 100644 htdocs/debugbar/css/widgets.css diff --git a/htdocs/core/class/hookmanager.class.php b/htdocs/core/class/hookmanager.class.php index 6c9cea6f6f75d..d8d29ec36b712 100644 --- a/htdocs/core/class/hookmanager.class.php +++ b/htdocs/core/class/hookmanager.class.php @@ -50,6 +50,11 @@ class HookManager // Array with instantiated classes public $hooks = array(); + /** + * @var array List of hooks called during this request + */ + public $hooksHistory = []; + // Array result public $resArray = array(); // Printable result @@ -155,6 +160,26 @@ public function initHooks($arraycontext) */ public function executeHooks($method, $parameters = array(), &$object = null, &$action = '') { + global $debugbar; + if (is_object($debugbar) && get_class($debugbar) === 'DolibarrDebugBar') { + $trace = debug_backtrace(); + if (isset($trace[0])) { + $hookInformations = [ + 'name' => $method, + 'contexts' => $this->contextarray, + 'file' => $trace[0]['file'], + 'line' => $trace[0]['line'], + ]; + $hash = md5(serialize($hookInformations)); + if (!empty($this->hooksHistory[$hash])) { + $this->hooksHistory[$hash]['count']++; + } else { + $hookInformations['count'] = 1; + $this->hooksHistory[$hash] = $hookInformations; + } + } + } + if (!is_array($this->hooks) || empty($this->hooks)) { return 0; // No hook available, do nothing. } diff --git a/htdocs/debugbar/class/DataCollector/DolHooksCollector.php b/htdocs/debugbar/class/DataCollector/DolHooksCollector.php new file mode 100644 index 0000000000000..7758c2e003cfc --- /dev/null +++ b/htdocs/debugbar/class/DataCollector/DolHooksCollector.php @@ -0,0 +1,73 @@ + []]; + if (empty($hookmanager->hooksHistory)) { + return $data; + } + $i = 0; + foreach ($hookmanager->hooksHistory as $key => $hookHistory) { + $i++; + $hookHistory['contexts'] = implode(', ', $hookHistory['contexts']); + $data['hooks']["[$i] {$hookHistory['name']}"] = $hookHistory; + +// $data["[$key] {$hookHistory['name']}"] = "{$hookHistory['file']} (L{$hookHistory['line']}). Contexts: " +// . implode(', ', $hookHistory['contexts']); + } + $data['nb_of_hooks'] = count($data['hooks']); + + return $data; + } + + /** + * Return widget settings + * + * @return string[][] + */ + public function getWidgets() + { + global $langs; + + $langs->load("other"); + + return [ + $langs->transnoentities('Hooks') => [ + "icon" => "tags", + "widget" => "PhpDebugBar.Widgets.HookListWidget", + "map" => "hooks.hooks", + "default" => "{}" + ], + "{$langs->transnoentities('Hooks')}:badge" => [ + "map" => "hooks.nb_of_hooks", + "default" => 0 + ] + ]; + } + + /** + * @return string + */ + public function getName() + { + return 'hooks'; + } +} diff --git a/htdocs/debugbar/class/DataCollector/DolibarrCollector.php b/htdocs/debugbar/class/DataCollector/DolibarrCollector.php index a114937ce5978..221cbc00163b9 100644 --- a/htdocs/debugbar/class/DataCollector/DolibarrCollector.php +++ b/htdocs/debugbar/class/DataCollector/DolibarrCollector.php @@ -172,7 +172,8 @@ public function getAssets() { return array( 'base_url' => dol_buildpath('/debugbar', 1), - 'js' => 'js/widgets.js' + 'js' => 'js/widgets.js', + 'css' => 'css/widgets.css' ); } } diff --git a/htdocs/debugbar/class/DebugBar.php b/htdocs/debugbar/class/DebugBar.php index fa7d4bd0e4ffa..9ad79d382e0d2 100644 --- a/htdocs/debugbar/class/DebugBar.php +++ b/htdocs/debugbar/class/DebugBar.php @@ -35,6 +35,7 @@ dol_include_once('/debugbar/class/DataCollector/DolQueryCollector.php'); dol_include_once('/debugbar/class/DataCollector/DolibarrCollector.php'); dol_include_once('/debugbar/class/DataCollector/DolLogsCollector.php'); +dol_include_once('/debugbar/class/DataCollector/DolHooksCollector.php'); /** * DolibarrDebugBar class @@ -62,6 +63,7 @@ public function __construct() //$this->addCollector(new DolExceptionsCollector()); $this->addCollector(new DolQueryCollector()); $this->addCollector(new DolibarrCollector()); + $this->addCollector(new DolHooksCollector()); if (isModEnabled('syslog')) { $this->addCollector(new DolLogsCollector()); } diff --git a/htdocs/debugbar/css/widgets.css b/htdocs/debugbar/css/widgets.css new file mode 100644 index 0000000000000..a93dbd7ca8f12 --- /dev/null +++ b/htdocs/debugbar/css/widgets.css @@ -0,0 +1,45 @@ +dl.phpdebugbar-widgets-kvlist.phpdebugbar-widgets-hooklist dt { + float: left; + width: 230px; + padding: 5px; + border-top: 1px solid #eee; + font-weight: bold; + clear: both; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +dl.phpdebugbar-widgets-kvlist.phpdebugbar-widgets-hooklist dt span { + font-weight: bold; +} +dl.phpdebugbar-widgets-kvlist dd { + margin-left: 240px; + padding: 5px; + border-top: 1px solid #eee; + cursor: pointer; + min-height: 17px; +} + +dl.phpdebugbar-widgets-kvlist dd span { + display: inline-block; +} + +dl.phpdebugbar-widgets-kvlist dd span strong{ + font-weight: bold; +} + +dl.phpdebugbar-widgets-kvlist dd span:nth-of-type(1) { + min-width: 550px; + margin-right: 20px; +} + +dl.phpdebugbar-widgets-kvlist dd span:nth-of-type(2) { + min-width: 90px; + margin-right: 20px; +} + +dl.phpdebugbar-widgets-kvlist dd span:nth-of-type(3) { + min-width: 80px; + margin-right: 20px; +} + diff --git a/htdocs/debugbar/js/widgets.js b/htdocs/debugbar/js/widgets.js index 7c10555a72e54..2b39fab99438a 100644 --- a/htdocs/debugbar/js/widgets.js +++ b/htdocs/debugbar/js/widgets.js @@ -72,4 +72,29 @@ }); -})(PhpDebugBar.$); \ No newline at end of file + /** + * An extension of KVListWidget where the data represents a list + * of variables + * + * Options: + * - data + */ + var HookListWidget = PhpDebugBar.Widgets.HookListWidget = PhpDebugBar.Widgets.KVListWidget.extend({ + + className: csscls('widgets-kvlist widgets-hooklist'), + + itemRenderer: function(dt, dd, key, object) { + $('').attr('title', key).text(key).appendTo(dt); + + + dd.html('File: ' + object.file + + 'Line: ' + object.line + + 'Count: ' + object.count + + 'Contexts: ' + (object.contexts === null || object.contexts === '' ? 'Not set' : object.contexts) + + '' + ); + } + }); + + +})(PhpDebugBar.$); From b172640ae507f1f97a0d88176d2cff4cd4d2433a Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 9 Apr 2024 03:38:38 +0200 Subject: [PATCH 3/5] Debug v20 --- htdocs/core/class/hookmanager.class.php | 15 ++-- htdocs/core/class/html.form.class.php | 4 +- .../class/DataCollector/DolHooksCollector.php | 79 ++++++++++++------- 3 files changed, 60 insertions(+), 38 deletions(-) diff --git a/htdocs/core/class/hookmanager.class.php b/htdocs/core/class/hookmanager.class.php index d8d29ec36b712..a7a4b6e090012 100644 --- a/htdocs/core/class/hookmanager.class.php +++ b/htdocs/core/class/hookmanager.class.php @@ -50,10 +50,10 @@ class HookManager // Array with instantiated classes public $hooks = array(); - /** - * @var array List of hooks called during this request - */ - public $hooksHistory = []; + /** + * @var array List of hooks called during this request + */ + public $hooksHistory = []; // Array result public $resArray = array(); @@ -160,8 +160,9 @@ public function initHooks($arraycontext) */ public function executeHooks($method, $parameters = array(), &$object = null, &$action = '') { - global $debugbar; - if (is_object($debugbar) && get_class($debugbar) === 'DolibarrDebugBar') { + //global $debugbar; + //if (is_object($debugbar) && get_class($debugbar) === 'DolibarrDebugBar') { + if (isModEnabled('debugbar') && function_exists('debug_backtrace')) { $trace = debug_backtrace(); if (isset($trace[0])) { $hookInformations = [ @@ -170,7 +171,7 @@ public function executeHooks($method, $parameters = array(), &$object = null, &$ 'file' => $trace[0]['file'], 'line' => $trace[0]['line'], ]; - $hash = md5(serialize($hookInformations)); + $hash = md5(json_encode($hookInformations)); if (!empty($this->hooksHistory[$hash])) { $this->hooksHistory[$hash]['count']++; } else { diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 92bec84e62509..c946683b8def8 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -8504,12 +8504,12 @@ public static function selectarray($htmlname, $array, $id = '', $show_empty = 0, foreach ($array as $key => $tmpvalue) { if (is_array($tmpvalue)) { $value = $tmpvalue['label']; - $valuehtml = $tmpvalue['data-html']; + //$valuehtml = empty($tmpvalue['data-html']) ? $value : $tmpvalue['data-html']; $disabled = empty($tmpvalue['disabled']) ? '' : ' disabled'; $style = empty($tmpvalue['css']) ? '' : ' class="' . $tmpvalue['css'] . '"'; } else { $value = $tmpvalue; - $valuehtml = $tmpvalue; + //$valuehtml = $tmpvalue; $disabled = ''; $style = ''; } diff --git a/htdocs/debugbar/class/DataCollector/DolHooksCollector.php b/htdocs/debugbar/class/DataCollector/DolHooksCollector.php index 7758c2e003cfc..c70cc41e8b0e7 100644 --- a/htdocs/debugbar/class/DataCollector/DolHooksCollector.php +++ b/htdocs/debugbar/class/DataCollector/DolHooksCollector.php @@ -1,11 +1,32 @@ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file htdocs/debugbar/class/DataCollector/DolHooksCollector.php + * \brief Class for debugbar collection + * \ingroup debugbar + */ + +use DebugBar\DataCollector\RequestDataCollector; -use \DebugBar\DataCollector\RequestDataCollector; /** * DolRequestDataCollector class */ - class DolHooksCollector extends RequestDataCollector { /** @@ -15,25 +36,25 @@ class DolHooksCollector extends RequestDataCollector */ public function collect() { - /** - * @global $hookmanager HookManager - */ - global $hookmanager; + /** + * @global $hookmanager HookManager + */ + global $hookmanager; - $data = ['hooks' => []]; - if (empty($hookmanager->hooksHistory)) { - return $data; - } + $data = ['hooks' => []]; + if (empty($hookmanager->hooksHistory)) { + return $data; + } $i = 0; foreach ($hookmanager->hooksHistory as $key => $hookHistory) { - $i++; + $i++; $hookHistory['contexts'] = implode(', ', $hookHistory['contexts']); - $data['hooks']["[$i] {$hookHistory['name']}"] = $hookHistory; + $data['hooks']["[$i] {$hookHistory['name']}"] = $hookHistory; -// $data["[$key] {$hookHistory['name']}"] = "{$hookHistory['file']} (L{$hookHistory['line']}). Contexts: " -// . implode(', ', $hookHistory['contexts']); + // $data["[$key] {$hookHistory['name']}"] = "{$hookHistory['file']} (L{$hookHistory['line']}). Contexts: " + // . implode(', ', $hookHistory['contexts']); } - $data['nb_of_hooks'] = count($data['hooks']); + $data['nb_of_hooks'] = count($data['hooks']); return $data; } @@ -42,7 +63,7 @@ public function collect() * Return widget settings * * @return string[][] - */ + */ public function getWidgets() { global $langs; @@ -55,19 +76,19 @@ public function getWidgets() "widget" => "PhpDebugBar.Widgets.HookListWidget", "map" => "hooks.hooks", "default" => "{}" - ], - "{$langs->transnoentities('Hooks')}:badge" => [ - "map" => "hooks.nb_of_hooks", - "default" => 0 - ] - ]; + ], + "{$langs->transnoentities('Hooks')}:badge" => [ + "map" => "hooks.nb_of_hooks", + "default" => 0 + ] + ]; } - /** - * @return string - */ - public function getName() - { - return 'hooks'; - } + /** + * @return string + */ + public function getName() + { + return 'hooks'; + } } From 46e21a0b2d3af29af3039e0f32c30a1c73c01581 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 9 Apr 2024 03:50:46 +0200 Subject: [PATCH 4/5] Removed dead code --- .../DataCollector/DolMessagesCollector.php | 57 ------------------- htdocs/debugbar/class/DebugBar.php | 3 - htdocs/debugbar/js/widgets.js | 1 - 3 files changed, 61 deletions(-) delete mode 100644 htdocs/debugbar/class/DataCollector/DolMessagesCollector.php diff --git a/htdocs/debugbar/class/DataCollector/DolMessagesCollector.php b/htdocs/debugbar/class/DataCollector/DolMessagesCollector.php deleted file mode 100644 index 3c605687b953b..0000000000000 --- a/htdocs/debugbar/class/DataCollector/DolMessagesCollector.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file htdocs/debugbar/class/DataCollector/DolMessagesCollector.php - * \brief Class for debugbar collection - * \ingroup debugbar - */ - -use DebugBar\DataCollector\MessagesCollector; - -/** - * DolMessagesCollector class - */ - -class DolMessagesCollector extends MessagesCollector -{ - /** - * Return widget settings - * - * @return array Array - */ - public function getWidgets() - { - global $langs; - - $title = $langs->transnoentities('Messages'); - $name = $this->getName(); - - return array( - "$title" => array( - "icon" => "list-alt", - "widget" => "PhpDebugBar.Widgets.MessagesWidget", - "map" => "$name.messages", - "default" => "[]" - ), - "$title:badge" => array( - "map" => "$name.count", - "default" => "null" - ) - ); - } -} diff --git a/htdocs/debugbar/class/DebugBar.php b/htdocs/debugbar/class/DebugBar.php index 9ad79d382e0d2..8ce3eb1573f29 100644 --- a/htdocs/debugbar/class/DebugBar.php +++ b/htdocs/debugbar/class/DebugBar.php @@ -25,7 +25,6 @@ use DebugBar\DebugBar; -dol_include_once('/debugbar/class/DataCollector/DolMessagesCollector.php'); dol_include_once('/debugbar/class/DataCollector/DolRequestDataCollector.php'); dol_include_once('/debugbar/class/DataCollector/DolConfigCollector.php'); dol_include_once('/debugbar/class/DataCollector/DolTimeDataCollector.php'); @@ -51,8 +50,6 @@ class DolibarrDebugBar extends DebugBar */ public function __construct() { - global $conf; - //$this->addCollector(new PhpInfoCollector()); //$this->addCollector(new DolMessagesCollector()); $this->addCollector(new DolRequestDataCollector()); diff --git a/htdocs/debugbar/js/widgets.js b/htdocs/debugbar/js/widgets.js index 2b39fab99438a..e8da2ae892a26 100644 --- a/htdocs/debugbar/js/widgets.js +++ b/htdocs/debugbar/js/widgets.js @@ -80,7 +80,6 @@ * - data */ var HookListWidget = PhpDebugBar.Widgets.HookListWidget = PhpDebugBar.Widgets.KVListWidget.extend({ - className: csscls('widgets-kvlist widgets-hooklist'), itemRenderer: function(dt, dd, key, object) { From 4bcbe4ebb1d4805a95a3a127afafb40341a82a9a Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 9 Apr 2024 04:03:23 +0200 Subject: [PATCH 5/5] CSS --- htdocs/debugbar/css/widgets.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/htdocs/debugbar/css/widgets.css b/htdocs/debugbar/css/widgets.css index a93dbd7ca8f12..761a1ee73f3fa 100644 --- a/htdocs/debugbar/css/widgets.css +++ b/htdocs/debugbar/css/widgets.css @@ -1,3 +1,7 @@ +span.phpdebugbar-widgets-value { + white-space: pre; +} + dl.phpdebugbar-widgets-kvlist.phpdebugbar-widgets-hooklist dt { float: left; width: 230px; @@ -42,4 +46,3 @@ dl.phpdebugbar-widgets-kvlist dd span:nth-of-type(3) { min-width: 80px; margin-right: 20px; } -