From 47680176620403624bf68ba3c79c1e5668f7ca3a Mon Sep 17 00:00:00 2001
From: Laurent Destailleur <eldy@destailleur.fr>
Date: Wed, 16 Nov 2016 19:23:32 +0100
Subject: [PATCH] Debug of module accountancy

---
 .../class/html.formventilation.class.php      | 113 ++++++++++--------
 htdocs/accountancy/customer/lines.php         |  19 ++-
 htdocs/accountancy/customer/list.php          |  17 +--
 htdocs/accountancy/expensereport/lines.php    |   7 +-
 htdocs/accountancy/expensereport/list.php     |  10 +-
 htdocs/accountancy/supplier/lines.php         |  21 ++--
 htdocs/accountancy/supplier/list.php          |  12 +-
 htdocs/core/lib/functions.lib.php             |  14 ++-
 test/phpunit/FunctionsLibTest.php             |   8 ++
 9 files changed, 135 insertions(+), 86 deletions(-)

diff --git a/htdocs/accountancy/class/html.formventilation.class.php b/htdocs/accountancy/class/html.formventilation.class.php
index e479b5931be..069e0ea3df3 100644
--- a/htdocs/accountancy/class/html.formventilation.class.php
+++ b/htdocs/accountancy/class/html.formventilation.class.php
@@ -30,6 +30,10 @@
  */
 class FormVentilation extends Form
 {
+    
+    private $options_cache = array();
+    
+    
 	/**
 	 * Return select filter with date of transaction
 	 *
@@ -70,63 +74,76 @@ class FormVentilation extends Form
 	 * @param int      $select_in          selectid value is a aa.rowid (0 default) or aa.account_number (1)
 	 * @param int      $select_out         set value returned by select 0=rowid (default), 1=account_number
 	 * @param string   $morecss            More css non HTML object
-	 * @return string String with HTML select
+	 * @param string   $usecache           Key to use to store result into a cache. Next call with same key will reuse the cache.
+	 * @return string                      String with HTML select
 	 */
-	function select_account($selectid, $htmlname = 'account', $showempty = 0, $event = array(), $select_in = 0, $select_out = 0, $morecss='maxwidth300 maxwidthonsmartphone') {
+	function select_account($selectid, $htmlname = 'account', $showempty = 0, $event = array(), $select_in = 0, $select_out = 0, $morecss='maxwidth300 maxwidthonsmartphone', $usecache='')
+	{
 		global $conf;
 
 		require_once DOL_DOCUMENT_ROOT . '/core/lib/accounting.lib.php';
 
-		$trunclength = defined('ACCOUNTING_LENGTH_DESCRIPTION_ACCOUNT') ? $conf->global->ACCOUNTING_LENGTH_DESCRIPTION_ACCOUNT : 50;
-
-		$sql = "SELECT DISTINCT aa.account_number, aa.label, aa.rowid, aa.fk_pcg_version";
-		$sql .= " FROM " . MAIN_DB_PREFIX . "accounting_account as aa";
-		$sql .= " INNER JOIN " . MAIN_DB_PREFIX . "accounting_system as asy ON aa.fk_pcg_version = asy.pcg_version";
-		$sql .= " AND asy.rowid = " . $conf->global->CHARTOFACCOUNTS;
-		$sql .= " AND aa.active = 1";
-		$sql .= " ORDER BY aa.account_number";
-
-		dol_syslog(get_class($this) . "::select_account", LOG_DEBUG);
-		$resql = $this->db->query($sql);
-
-		if (!$resql) {
-			$this->error = "Error " . $this->db->lasterror();
-			dol_syslog(get_class($this) . "::select_account " . $this->error, LOG_ERR);
-			return -1;
+    	$options = array();
+		if ($usecache && ! empty($this->options_cache[$usecache]))
+		{
+		    $options = $this->options_cache[$usecache];
 		}
-
-		$out = ajax_combobox($htmlname, $event);
-
-		// TODO Add $options in cache so next call will not execute the request
-		$selected = 0;
-		$options = array();
-		while ($obj = $this->db->fetch_object($resql)) 
+		else
 		{
-			$label = length_accountg($obj->account_number) . ' - ' . $obj->label;
-			$label = dol_trunc($label, $trunclength);
-
-			$select_value_in = $obj->rowid;
-			$select_value_out = $obj->rowid;
-
-			// Try to guess if we have found default value
-			if ($select_in == 1) {
-				$select_value_in = $obj->account_number;
-			}
-			if ($select_out == 1) {
-				$select_value_out = $obj->account_number;
-			}
-			// Remember guy's we store in database llx_facturedet the rowid of accounting_account and not the account_number
-			// Because same account_number can be share between different accounting_system and do have the same meaning
-			if ($selectid != '' && $selectid == $select_value_in) {
-			    //var_dump("Found ".$selectid." ".$select_value_in);
-				$selected = $select_value_out;
-			}
-
-			$options[$select_value_out] = $label;
+    		$trunclength = defined('ACCOUNTING_LENGTH_DESCRIPTION_ACCOUNT') ? $conf->global->ACCOUNTING_LENGTH_DESCRIPTION_ACCOUNT : 50;
+    
+    		$sql = "SELECT DISTINCT aa.account_number, aa.label, aa.rowid, aa.fk_pcg_version";
+    		$sql .= " FROM " . MAIN_DB_PREFIX . "accounting_account as aa";
+    		$sql .= " INNER JOIN " . MAIN_DB_PREFIX . "accounting_system as asy ON aa.fk_pcg_version = asy.pcg_version";
+    		$sql .= " AND asy.rowid = " . $conf->global->CHARTOFACCOUNTS;
+    		$sql .= " AND aa.active = 1";
+    		$sql .= " ORDER BY aa.account_number";
+    
+    		dol_syslog(get_class($this) . "::select_account", LOG_DEBUG);
+    		$resql = $this->db->query($sql);
+    
+    		if (!$resql) {
+    			$this->error = "Error " . $this->db->lasterror();
+    			dol_syslog(get_class($this) . "::select_account " . $this->error, LOG_ERR);
+    			return -1;
+    		}
+
+    		$out = ajax_combobox($htmlname, $event);
+    
+    		$selected = 0;
+    		while ($obj = $this->db->fetch_object($resql)) 
+    		{
+    			$label = length_accountg($obj->account_number) . ' - ' . $obj->label;
+    			$label = dol_trunc($label, $trunclength);
+    
+    			$select_value_in = $obj->rowid;
+    			$select_value_out = $obj->rowid;
+    
+    			// Try to guess if we have found default value
+    			if ($select_in == 1) {
+    				$select_value_in = $obj->account_number;
+    			}
+    			if ($select_out == 1) {
+    				$select_value_out = $obj->account_number;
+    			}
+    			// Remember guy's we store in database llx_facturedet the rowid of accounting_account and not the account_number
+    			// Because same account_number can be share between different accounting_system and do have the same meaning
+    			if ($selectid != '' && $selectid == $select_value_in) {
+    			    //var_dump("Found ".$selectid." ".$select_value_in);
+    				$selected = $select_value_out;
+    			}
+    
+    			$options[$select_value_out] = $label;
+    		}
+    		$this->db->free($resql);
+    		
+    		if ($usecache)
+    		{
+                $this->options_cache[$usecache] = $options;
+    		}
 		}
-
+		
 		$out .= Form::selectarray($htmlname, $options, $selected, $showempty, 0, 0, '', 0, 0, 0, '', $morecss, 1);
-		$this->db->free($resql);
 		
 		return $out;
 	}
diff --git a/htdocs/accountancy/customer/lines.php b/htdocs/accountancy/customer/lines.php
index 2e3bdf6b6fc..71f52ec48a7 100644
--- a/htdocs/accountancy/customer/lines.php
+++ b/htdocs/accountancy/customer/lines.php
@@ -263,7 +263,7 @@ if ($result) {
 	print_liste_field_titre($langs->trans("Description"), $_SERVER["PHP_SELF"], "fd.description", "", $param, '', $sortfield, $sortorder);
 	print_liste_field_titre($langs->trans("Amount"), $_SERVER["PHP_SELF"], "fd.total_ht", "", $param, 'align="right"', $sortfield, $sortorder);
 	print_liste_field_titre($langs->trans("VATRate"), $_SERVER["PHP_SELF"], "fd.tva_tx", "", $param, 'align="center"', $sortfield, $sortorder);
-	print_liste_field_titre($langs->trans("Account"), $_SERVER["PHP_SELF"], "aa.account_number", "", $param, 'align="center"', $sortfield, $sortorder);
+	print_liste_field_titre($langs->trans("Account"), $_SERVER["PHP_SELF"], "aa.account_number", "", $param, '', $sortfield, $sortorder);
 	print_liste_field_titre($langs->trans("Country"), $_SERVER["PHP_SELF"], "co.label", "", $param, '', $sortfield, $sortorder);
 	print_liste_field_titre($langs->trans("VATIntra"), $_SERVER["PHP_SELF"], "s.tva_intra", "", $param, '', $sortfield, $sortorder);
 	print_liste_field_titre('', '', '', '', '', 'align="center"');
@@ -278,7 +278,7 @@ if ($result) {
 	print '<td class="liste_titre"><input type="text" class="flat maxwidth50" name="search_desc" value="' . dol_escape_htmltag($search_desc) . '"></td>';
 	print '<td class="liste_titre" align="right"><input type="text" class="flat maxwidth50" name="search_amount" value="' . dol_escape_htmltag($search_amount) . '"></td>';
 	print '<td class="liste_titre" align="center"><input type="text" class="flat maxwidth50" name="search_vat" size="1" value="' . dol_escape_htmltag($search_vat) . '"></td>';
-	print '<td class="liste_titre" align="center"><input type="text" class="flat maxwidth50" name="search_account" value="' . dol_escape_htmltag($search_account) . '"></td>';
+	print '<td class="liste_titre"><input type="text" class="flat maxwidth50" name="search_account" value="' . dol_escape_htmltag($search_account) . '"></td>';
 	print '<td class="liste_titre"><input type="text" class="flat maxwidth50" name="search_country" value="' . dol_escape_htmltag($search_country) . '"></td>';
 	print '<td class="liste_titre"><input type="text" class="flat maxwidth50" name="search_tavintra" value="' . dol_escape_htmltag($search_tavintra) . '"></td>';
 	print '<td class="liste_titre" align="right">';
@@ -309,7 +309,7 @@ if ($result) {
 		// Ref Invoice
 		print '<td>' . $facture_static->getNomUrl(1) . '</td>';
 
-		print '<td align="center">' . dol_print_date($objp->datef, 'day') . '</td>';
+		print '<td align="center">' . dol_print_date($db->jdate($objp->datef), 'day') . '</td>';
 		
 		// Ref Product
 		print '<td>';
@@ -318,12 +318,19 @@ if ($result) {
 		if ($objp->product_label) print '<br>'.$objp->product_label;
 		print '</td>';
 		
-		print '<td>' . nl2br(dol_trunc($objp->description, 32)) . '</td>';
+		print '<td>';
+		$text = dolGetFirstLineOfText(dol_string_nohtmltag($objp->description));
+		$trunclength = defined('ACCOUNTING_LENGTH_DESCRIPTION') ? ACCOUNTING_LENGTH_DESCRIPTION : 32;
+		print $form->textwithtooltip(dol_trunc($text,$trunclength), $objp->description);
+		print '</td>';
+		
 		print '<td align="right">' . price($objp->total_ht) . '</td>';
 		print '<td align="center">' . price($objp->tva_tx) . '</td>';
-		print '<td>' . $codecompta . '<a href="./card.php?id=' . $objp->fdid . '">';
+		print '<td>';
+		print $codecompta . ' <a href="./card.php?id=' . $objp->fdid . '">';
 		print img_edit();
-		print '</a></td>';
+		print '</a>';
+		print '</td>';
 		print '<td>' . $objp->country .'</td>';
 		print '<td>' . $objp->tva_intra . '</td>';
 		print '<td align="right"><input type="checkbox" class="checkforaction" name="changeaccount[]" value="' . $objp->fdid . '"/></td>';
diff --git a/htdocs/accountancy/customer/list.php b/htdocs/accountancy/customer/list.php
index 94d68544c72..aa1c2742a08 100644
--- a/htdocs/accountancy/customer/list.php
+++ b/htdocs/accountancy/customer/list.php
@@ -287,6 +287,10 @@ if ($result) {
 		$product_static->id = $objp->product_id;
 		$product_static->type = $objp->type;
 		$product_static->label = $objp->product_label;
+
+		$facture_static->ref = $objp->facnumber;
+		$facture_static->id = $objp->facid;
+		$facture_static->type = $objp->ftype;
 		
 		$code_sell_p_notset = '';
 		$objp->aarowid_suggest = $objp->aarowid;
@@ -320,12 +324,9 @@ if ($result) {
 		print '<td>' . $objp->rowid . '</td>';
 		
 		// Ref Invoice
-		$facture_static->ref = $objp->facnumber;
-		$facture_static->id = $objp->facid;
-		$facture_static->type = $objp->ftype;
 		print '<td>' . $facture_static->getNomUrl(1) . '</td>';
 
-		print '<td align="center">' . dol_print_date($objp->datef, 'day') . '</td>';
+		print '<td align="center">' . dol_print_date($db->jdate($objp->datef), 'day') . '</td>';
 		
 		// Ref Product
 		print '<td>';
@@ -334,9 +335,11 @@ if ($result) {
 		if ($objp->product_label) print '<br>'.$objp->product_label;
 		print '</td>';
 		
-		
+		print '<td class="tdoverflow">';
+		$text = dolGetFirstLineOfText(dol_string_nohtmltag($objp->description));
 		$trunclength = defined('ACCOUNTING_LENGTH_DESCRIPTION') ? ACCOUNTING_LENGTH_DESCRIPTION : 32;
-		print '<td class="tdoverflow">' . nl2br(dol_trunc($objp->description, $trunclength)) . '</td>';
+		print $form->textwithtooltip(dol_trunc($text,$trunclength), $objp->description);
+		print '</td>';
 		
 		print '<td align="right">';
 		print price($objp->total_ht);
@@ -360,7 +363,7 @@ if ($result) {
 		print '</td>';
 
 		print '<td align="center">';
-		print $formventilation->select_account($objp->aarowid_suggest, 'codeventil'.$objp->rowid, 1);
+		print $formventilation->select_account($objp->aarowid_suggest, 'codeventil'.$objp->rowid, 1, array(), 0, 0, 'maxwidth300 maxwidthonsmartphone', 'cachewithshowemptyone');
 		print '</td>';
 		
 		print '<td align="right">';
diff --git a/htdocs/accountancy/expensereport/lines.php b/htdocs/accountancy/expensereport/lines.php
index d42ee8442ed..29a8b167b87 100644
--- a/htdocs/accountancy/expensereport/lines.php
+++ b/htdocs/accountancy/expensereport/lines.php
@@ -279,12 +279,15 @@ if ($result) {
 		// Ref Invoice
 		print '<td>' . $expensereport_static->getNomUrl(1) . '</td>';
 
-		print '<td align="center">' . dol_print_date($objp->date, 'day') . '</td>';
+		print '<td align="center">' . dol_print_date($db->jdate($objp->date), 'day') . '</td>';
 		
 		print '<td class="tdoverflow">' . $objp->fees_label . '</td>';
 
+		print '<td>';
+		$text = dolGetFirstLineOfText(dol_string_nohtmltag($objp->comments));
 		$trunclength = defined('ACCOUNTING_LENGTH_DESCRIPTION') ? ACCOUNTING_LENGTH_DESCRIPTION : 32;
-		print '<td>' . nl2br(dol_trunc($objp->comments, $trunclength)) . '</td>';
+		print $form->textwithtooltip(dol_trunc($text,$trunclength), $objp->comments);
+		print '</td>';
 
 		print '<td align="right">' . price($objp->total_ht) . '</td>';
 
diff --git a/htdocs/accountancy/expensereport/list.php b/htdocs/accountancy/expensereport/list.php
index 103dd7f98d3..ef7acac9ad0 100644
--- a/htdocs/accountancy/expensereport/list.php
+++ b/htdocs/accountancy/expensereport/list.php
@@ -282,7 +282,7 @@ if ($result) {
 		// Line id
 		print '<td>' . $objp->rowid . '</td>';
 
-		print '<td align="center">' . dol_print_date($objp->date, 'day') . '</td>';
+		print '<td align="center">' . dol_print_date($db->jdate($objp->date), 'day') . '</td>';
 		
 		// Ref Expense report
 		print '<td>' . $expensereport_static->getNomUrl(1) . '</td>';
@@ -293,9 +293,11 @@ if ($result) {
 		print '</td>';
 
 		// Fees description -- Can be null
-		// TODO: we should set a user defined value to adjust user square / wide screen size
+		print '<td>';
+		$text = dolGetFirstLineOfText(dol_string_nohtmltag($objp->comments));
 		$trunclength = defined('ACCOUNTING_LENGTH_DESCRIPTION') ? ACCOUNTING_LENGTH_DESCRIPTION : 32;
-		print '<td>' . nl2br(dol_trunc($objp->comments, $trunclength)) . '</td>';
+		print $form->textwithtooltip(dol_trunc($text,$trunclength), $objp->comments);
+		print '</td>';
 
 		print '<td align="right">';
 		print price($objp->price);
@@ -313,7 +315,7 @@ if ($result) {
 
 		// Colonne choix du compte
 		print '<td align="center">';
-		print $formventilation->select_account($objp->aarowid_suggest, 'codeventil'.$objp->rowid, 1);
+		print $formventilation->select_account($objp->aarowid_suggest, 'codeventil'.$objp->rowid, 1, array(), 0, 0, 'maxwidth300 maxwidthonsmartphone', 'cachewithshowemptyone');
 		print '</td>';
 
 		// Colonne choix ligne a ventiler
diff --git a/htdocs/accountancy/supplier/lines.php b/htdocs/accountancy/supplier/lines.php
index 7b5e3bd7f24..a2cdc7b42cc 100644
--- a/htdocs/accountancy/supplier/lines.php
+++ b/htdocs/accountancy/supplier/lines.php
@@ -248,8 +248,7 @@ if ($result) {
 	print_liste_field_titre($langs->trans("Description"), $_SERVER["PHP_SELF"], "l.description", "", $param, '', $sortfield, $sortorder);
 	print_liste_field_titre($langs->trans("Amount"), $_SERVER["PHP_SELF"], "l.total_ht", "", $param, 'align="right"', $sortfield, $sortorder);
 	print_liste_field_titre($langs->trans("VATRate"), $_SERVER["PHP_SELF"], "l.tva_tx", "", $param, 'align="center"', $sortfield, $sortorder);
-	print_liste_field_titre($langs->trans("Account"), $_SERVER["PHP_SELF"], "aa.account_number", "", $param, 'align="center"', $sortfield, $sortorder);
-	print_liste_field_titre('');
+	print_liste_field_titre($langs->trans("Account"), $_SERVER["PHP_SELF"], "aa.account_number", "", $param, '', $sortfield, $sortorder);
 	print_liste_field_titre('', '', '', '', '', 'align="center"');
 	print "</tr>\n";
 	
@@ -263,8 +262,7 @@ if ($result) {
 	print '<td class="liste_titre"><input type="text" class="flat maxwidth50" name="search_desc" value="' . dol_escape_htmltag($search_desc) . '"></td>';
 	print '<td class="liste_titre" align="right"><input type="text" class="flat maxwidth50" name="search_amount" value="' . dol_escape_htmltag($search_amount) . '"></td>';
 	print '<td class="liste_titre" align="center"><input type="text" class="flat maxwidth50" name="search_vat" size="1" value="' . dol_escape_htmltag($search_vat) . '"></td>';
-	print '<td class="liste_titre" align="center"><input type="text" class="flat maxwidth50" name="search_account" value="' . dol_escape_htmltag($search_account) . '"></td>';
-	print '<td class="liste_titre" align="right"></td>';
+	print '<td class="liste_titre"><input type="text" class="flat maxwidth50" name="search_account" value="' . dol_escape_htmltag($search_account) . '"></td>';
     print '<td class="liste_titre" align="right">';
     $searchpitco=$form->showFilterAndCheckAddButtons(1);
     print $searchpitco;
@@ -278,7 +276,7 @@ if ($result) {
 	while ( $i < min($num_lines, $limit) ) {
 		$objp = $db->fetch_object($result);
 		$var = ! $var;
-		$codeCompta = length_accountg($objp->account_number) . ' - ' . $objp->label;
+		$codecompta = length_accountg($objp->account_number) . ' - ' . $objp->label;
 		
 		$facturefournisseur_static->ref = $objp->facnumber;
 		$facturefournisseur_static->id = $objp->facid;
@@ -299,7 +297,7 @@ if ($result) {
 		print $objp->invoice_label;
 		print '</td>';
 		
-		print '<td align="center">' . dol_print_date($objp->datef, 'day') . '</td>';
+		print '<td align="center">' . dol_print_date($db->jdate($objp->datef), 'day') . '</td>';
 		
 		// Ref Product
 		print '<td>';
@@ -308,11 +306,16 @@ if ($result) {
 	    if ($objp->product_label) print '<br>'.$objp->product_label;
 		print '</td>';
 		
-		print '<td>' . nl2br(dol_trunc($objp->description, 32)) . '</td>';
+		print '<td>';
+		$text = dolGetFirstLineOfText(dol_string_nohtmltag($objp->description));
+		$trunclength = defined('ACCOUNTING_LENGTH_DESCRIPTION') ? ACCOUNTING_LENGTH_DESCRIPTION : 32;
+		print $form->textwithtooltip(dol_trunc($text,$trunclength), $objp->description);
+		print '</td>';
+		
 		print '<td align="right">' . price($objp->total_ht) . '</td>';
 		print '<td align="center">' . price($objp->tva_tx) . '</td>';
-		print '<td>' . $codeCompta . '</td>';
-		print '<td align="left"><a href="./card.php?id=' . $objp->rowid . '">';
+		print '<td align="left">';
+		print $codecompta . ' <a href="./card.php?id=' . $objp->rowid . '">';
 		print img_edit();
 		print '</a></td>';
 		
diff --git a/htdocs/accountancy/supplier/list.php b/htdocs/accountancy/supplier/list.php
index 47510b98b2b..e59fb91cf23 100644
--- a/htdocs/accountancy/supplier/list.php
+++ b/htdocs/accountancy/supplier/list.php
@@ -300,6 +300,7 @@ if ($result) {
 		
 		$facturefourn_static->ref = $objp->ref;
 		$facturefourn_static->id = $objp->facid;
+		$facturefourn_static->type = $objp->type;
 		
 		$code_buy_p_notset = '';
 		$objp->aarowid_suggest = $objp->aarowid;
@@ -337,7 +338,7 @@ if ($result) {
 		print $objp->invoice_label;
 		print '</td>';
 		
-		print '<td align="center">' . dol_print_date($objp->datef, 'day') . '</td>';
+		print '<td align="center">' . dol_print_date($db->jdate($objp->datef), 'day') . '</td>';
 		
 		// Ref product
 		print '<td>';
@@ -346,9 +347,12 @@ if ($result) {
 		if ($objp->product_label) print '<br>'.$objp->product_label;
         print '</td>';
         
-		// TODO: we should set a user defined value to adjust user square / wide screen size
+        // Description
+		print '<td>';
+		$text = dolGetFirstLineOfText(dol_string_nohtmltag($objp->description));
 		$trunclength = defined('ACCOUNTING_LENGTH_DESCRIPTION') ? ACCOUNTING_LENGTH_DESCRIPTION : 32;
-		print '<td>' . nl2br(dol_trunc($objp->description, $trunclength)) . '</td>';
+		print $form->textwithtooltip(dol_trunc($text,$trunclength), $objp->description);
+		print '</td>';
 
 		print '<td align="right">';
 		print price($objp->price);
@@ -373,7 +377,7 @@ if ($result) {
 
 		// Suggested accounting account
 		print '<td align="center">';
-		print $formventilation->select_account($objp->aarowid_suggest, 'codeventil'.$objp->rowid, 1);
+		print $formventilation->select_account($objp->aarowid_suggest, 'codeventil'.$objp->rowid, 1, array(), 0, 0, 'maxwidth300 maxwidthonsmartphone', 'cachewithshowemptyone');
 		print '</td>';
 		
 		// Colonne choix ligne a ventiler
diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php
index db1d70b4899..4f68b32f171 100644
--- a/htdocs/core/lib/functions.lib.php
+++ b/htdocs/core/lib/functions.lib.php
@@ -619,17 +619,18 @@ function dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
  *  Returns text escaped for inclusion in HTML alt or title tags, or into values of HTML input fields.
  *
  *  @param      string		$stringtoescape		String to escape
- *  @param		int			$keepb				Do not clean b tags
+ *  @param		int			$keepb				1=Preserve b tags (otherwise, remove them)
+ *  @param      int         $keepn              1=Preserve \r\n strings (otherwise, remove them)
  *  @return     string     				 		Escaped string
  *
  *  @see		dol_string_nohtmltag
  */
-function dol_escape_htmltag($stringtoescape,$keepb=0)
+function dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0)
 {
 	// escape quotes and backslashes, newlines, etc.
 	$tmp=dol_html_entity_decode($stringtoescape,ENT_COMPAT,'UTF-8');
-	if ($keepb) $tmp=strtr($tmp, array("\r"=>'\\r',"\n"=>'\\n'));
-	else $tmp=strtr($tmp, array("\r"=>'\\r',"\n"=>'\\n',"<b>"=>'','</b>'=>''));
+	if (! $keepb) $tmp=strtr($tmp, array("<b>"=>'','</b>'=>''));
+	if (! $keepn) $tmp=strtr($tmp, array("\r"=>'\\r',"\n"=>'\\n'));
 	return dol_htmlentities($tmp,ENT_COMPAT,'UTF-8');
 }
 
@@ -4395,7 +4396,7 @@ function picto_required()
  *	Clean a string from all HTML tags and entities
  *
  *	@param	string	$StringHtml			String to clean
- *	@param	integer	$removelinefeed		1=Replace also all lines feeds by a space, 0=Only last one are removed
+ *	@param	integer	$removelinefeed		1=Replace also new lines by a space, 0=Only last one are removed
  *  @param  string	$pagecodeto      	Encoding of input/output string
  *	@return string	    				String cleaned
  *
@@ -4404,6 +4405,7 @@ function picto_required()
 function dol_string_nohtmltag($StringHtml,$removelinefeed=1,$pagecodeto='UTF-8')
 {
 	$pattern = "/<[^<>]+>/";
+	$StringHtml = preg_replace('/<br[^>]*>/', "\n", $StringHtml);
 	$temp = dol_html_entity_decode($StringHtml,ENT_COMPAT,$pagecodeto);
 	$temp = preg_replace($pattern,"",$temp);
 
@@ -4425,7 +4427,7 @@ function dol_string_nohtmltag($StringHtml,$removelinefeed=1,$pagecodeto='UTF-8')
  *
  * @param 	string	$text		Input text
  * @return	string				Output text
- * @see dol_nboflines_bis
+ * @see dol_nboflines_bis, dol_string_nohtmltag, dol_escape_htmltag
  */
 function dolGetFirstLineOfText($text)
 {
diff --git a/test/phpunit/FunctionsLibTest.php b/test/phpunit/FunctionsLibTest.php
index f967b53d9db..786970e2b3a 100644
--- a/test/phpunit/FunctionsLibTest.php
+++ b/test/phpunit/FunctionsLibTest.php
@@ -367,6 +367,14 @@ class FunctionsLibTest extends PHPUnit_Framework_TestCase
         $after=dol_string_nohtmltag($text, 1);
         $this->assertEquals("A string with tag with < chars",$after,"test3");
 
+        $text="A string<br>Another string";
+        $after=dol_string_nohtmltag($text,0);
+        $this->assertEquals("A string\nAnother string",$after,"test4");
+
+        $text="A string<br>Another string";
+        $after=dol_string_nohtmltag($text,1);
+        $this->assertEquals("A string Another string",$after,"test5");
+
         return true;
     }
 
-- 
GitLab