Browse Source

Merge branch '18.0' of git@github.com:Dolibarr/dolibarr.git into 19.0

Laurent Destailleur (aka Eldy) 4 months ago
parent
commit
1f360ca498
42 changed files with 426 additions and 97 deletions
  1. 7 1
      htdocs/adherents/class/api_members.class.php
  2. 7 2
      htdocs/adherents/class/api_memberstypes.class.php
  3. 7 1
      htdocs/adherents/class/api_subscriptions.class.php
  4. 7 1
      htdocs/bom/class/api_boms.class.php
  5. 7 1
      htdocs/categories/class/api_categories.class.php
  6. 2 2
      htdocs/comm/action/class/actioncomm.class.php
  7. 6 0
      htdocs/comm/action/class/api_agendaevents.class.php
  8. 7 1
      htdocs/comm/propal/class/api_proposals.class.php
  9. 7 1
      htdocs/commande/class/api_orders.class.php
  10. 6 0
      htdocs/compta/bank/class/api_bankaccounts.class.php
  11. 7 1
      htdocs/compta/facture/class/api_invoices.class.php
  12. 7 1
      htdocs/contrat/class/api_contracts.class.php
  13. 1 1
      htdocs/core/class/CMailFile.class.php
  14. 15 12
      htdocs/core/lib/functions.lib.php
  15. 48 2
      htdocs/core/lib/pdf.lib.php
  16. 106 26
      htdocs/core/triggers/interface_50_modTicket_TicketEmail.class.php
  17. 7 1
      htdocs/don/class/api_donations.class.php
  18. 7 1
      htdocs/expedition/class/api_shipments.class.php
  19. 7 1
      htdocs/expensereport/class/api_expensereports.class.php
  20. 1 0
      htdocs/expensereport/class/expensereport.class.php
  21. 7 0
      htdocs/fourn/class/api_supplier_invoices.class.php
  22. 7 0
      htdocs/fourn/class/api_supplier_orders.class.php
  23. 2 2
      htdocs/includes/odtphp/odf.php
  24. 7 0
      htdocs/knowledgemanagement/class/api_knowledgemanagement.class.php
  25. 2 2
      htdocs/loan/card.php
  26. 1 0
      htdocs/loan/class/loan.class.php
  27. 1 1
      htdocs/loan/class/paymentloan.class.php
  28. 7 0
      htdocs/modulebuilder/template/class/api_mymodule.class.php
  29. 8 1
      htdocs/mrp/class/api_mos.class.php
  30. 6 0
      htdocs/partnership/class/api_partnerships.class.php
  31. 8 1
      htdocs/product/class/api_products.class.php
  32. 7 1
      htdocs/product/stock/class/api_warehouses.class.php
  33. 7 1
      htdocs/projet/class/api_projects.class.php
  34. 7 1
      htdocs/projet/class/api_tasks.class.php
  35. 7 1
      htdocs/reception/class/api_receptions.class.php
  36. 1 1
      htdocs/societe/class/api_contacts.class.php
  37. 6 0
      htdocs/societe/class/api_thirdparties.class.php
  38. 1 4
      htdocs/societe/list.php
  39. 10 1
      htdocs/ticket/class/api_tickets.class.php
  40. 7 1
      htdocs/user/class/api_users.class.php
  41. 27 14
      htdocs/variants/class/ProductCombination.class.php
  42. 21 9
      test/phpunit/ODFTest.php

+ 7 - 1
htdocs/adherents/class/api_members.class.php

@@ -358,7 +358,13 @@ class Members extends DolibarrApi
 					}
 				}
 			} else {
-				$member->$field = $value;
+				if ($field == 'array_options' && is_array($value)) {
+					foreach ($value as $index => $val) {
+						$member->array_options[$index] = $this->_checkValForAPI($field, $val, $member);
+					}
+					continue;
+				}
+				$member->$field = $this->_checkValForAPI($field, $value, $member);
 			}
 		}
 

+ 7 - 2
htdocs/adherents/class/api_memberstypes.class.php

@@ -202,10 +202,15 @@ class MembersTypes extends DolibarrApi
 				$membertype->context['caller'] = $request_data['caller'];
 				continue;
 			}
-
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$membertype->array_options[$index] = $this->_checkValForAPI($field, $val, $membertype);
+				}
+				continue;
+			}
 			// Process the status separately because it must be updated using
 			// the validate(), resiliate() and exclude() methods of the class AdherentType.
-			$membertype->$field = $value;
+			$membertype->$field = $this->_checkValForAPI($field, $value, $membertype);
 		}
 
 		// If there is no error, update() returns the number of affected rows

+ 7 - 1
htdocs/adherents/class/api_subscriptions.class.php

@@ -196,7 +196,13 @@ class Subscriptions extends DolibarrApi
 				continue;
 			}
 
-			$subscription->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$subscription->array_options[$index] = $this->_checkValForAPI($field, $val, $subscription);
+				}
+				continue;
+			}
+			$subscription->$field = $this->_checkValForAPI($field, $val, $subscription);
 		}
 
 		if ($subscription->update(DolibarrApiAccess::$user) > 0) {

+ 7 - 1
htdocs/bom/class/api_boms.class.php

@@ -251,7 +251,13 @@ class Boms extends DolibarrApi
 				continue;
 			}
 
-			$this->bom->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->bom->array_options[$index] = $this->_checkValForAPI($field, $val, $this->bom);
+				}
+				continue;
+			}
+			$this->bom->$field = $this->_checkValForAPI($field, $value, $this->bom);
 		}
 
 		$this->checkRefNumbering();

+ 7 - 1
htdocs/categories/class/api_categories.class.php

@@ -248,7 +248,13 @@ class Categories extends DolibarrApi
 				continue;
 			}
 
-			$this->category->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->category->array_options[$index] = $this->_checkValForAPI($field, $val, $this->category);
+				}
+				continue;
+			}
+			$this->category->$field = $this->_checkValForAPI($field, $value, $this->category);
 		}
 
 		if ($this->category->update(DolibarrApiAccess::$user) > 0) {

+ 2 - 2
htdocs/comm/action/class/actioncomm.class.php

@@ -629,7 +629,7 @@ class ActionComm extends CommonObject
 				foreach ($this->userassigned as $key => $val) {
 					// Common value with new behavior is to have $val = array('id'=>iduser, 'transparency'=>0|1) and $this->userassigned is an array of iduser => $val.
 					if (!is_array($val)) {	// For backward compatibility when $val='id'.
-						$val = array('id'=>$val);
+						$val = array('id' => $val);
 					}
 
 					if ($val['id'] > 0) {
@@ -1271,7 +1271,7 @@ class ActionComm extends CommonObject
 					$already_inserted = array();
 					foreach (array_keys($this->socpeopleassigned) as $key => $val) {
 						if (!is_array($val)) {	// For backward compatibility when val=id
-							$val = array('id'=>$val);
+							$val = array('id' => $val);
 						}
 						if (!empty($already_inserted[$val['id']])) {
 							continue;

+ 6 - 0
htdocs/comm/action/class/api_agendaevents.class.php

@@ -279,6 +279,12 @@ class AgendaEvents extends DolibarrApi
 				continue;
 			}
 
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->actioncomm->array_options[$index] = $this->_checkValForAPI($field, $val, $this->actioncomm);
+				}
+				continue;
+			}
 			$this->actioncomm->$field = $this->_checkValForAPI($field, $value, $this->actioncomm);
 		}
 

+ 7 - 1
htdocs/comm/propal/class/api_proposals.class.php

@@ -708,7 +708,13 @@ class Proposals extends DolibarrApi
 				continue;
 			}
 
-			$this->propal->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->propal->array_options[$index] = $this->_checkValForAPI($field, $val, $this->propal);
+				}
+				continue;
+			}
+			$this->propal->$field = $this->_checkValForAPI($field, $value, $this->propal);
 		}
 
 		// update end of validity date

+ 7 - 1
htdocs/commande/class/api_orders.class.php

@@ -662,7 +662,13 @@ class Orders extends DolibarrApi
 				continue;
 			}
 
-			$this->commande->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->commande->array_options[$index] = $this->_checkValForAPI($field, $val, $this->commande);
+				}
+				continue;
+			}
+			$this->commande->$field = $this->_checkValForAPI($field, $value, $this->commande);
 		}
 
 		// Update availability

+ 6 - 0
htdocs/compta/bank/class/api_bankaccounts.class.php

@@ -345,6 +345,12 @@ class BankAccounts extends DolibarrApi
 				continue;
 			}
 
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$account->array_options[$index] = $this->_checkValForAPI($field, $val, $account);
+				}
+				continue;
+			}
 			$account->$field = $this->_checkValForAPI($field, $value, $account);
 		}
 

+ 7 - 1
htdocs/compta/facture/class/api_invoices.class.php

@@ -633,7 +633,13 @@ class Invoices extends DolibarrApi
 				continue;
 			}
 
-			$this->invoice->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->invoice->array_options[$index] = $this->_checkValForAPI($field, $val, $this->invoice);
+				}
+				continue;
+			}
+			$this->invoice->$field = $this->_checkValForAPI($field, $value, $this->invoice);
 		}
 
 		// update bank account

+ 7 - 1
htdocs/contrat/class/api_contracts.class.php

@@ -519,7 +519,13 @@ class Contracts extends DolibarrApi
 				continue;
 			}
 
-			$this->contract->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->contract->array_options[$index] = $this->_checkValForAPI($field, $val, $this->contract);
+				}
+				continue;
+			}
+			$this->contract->$field = $this->_checkValForAPI($field, $value, $this->contract);
 		}
 
 		if ($this->contract->update(DolibarrApiAccess::$user) > 0) {

+ 1 - 1
htdocs/core/class/CMailFile.class.php

@@ -694,7 +694,7 @@ class CMailFile
 				$this->error = "Error in hook maildao sendMail ".$reshook;
 				dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
 
-				return $reshook;
+				return false;
 			}
 			if ($reshook == 1) {	// Hook replace standard code
 				return true;

+ 15 - 12
htdocs/core/lib/functions.lib.php

@@ -9557,10 +9557,11 @@ function dol_osencode($str)
  * 		@param	string	$fieldid		Field to get
  *      @param  int		$entityfilter	Filter by entity
  *      @param	string	$filters		Filters to add. WARNING: string must be escaped for SQL and not coming from user input.
+ *      @param	bool    $useCache       If true (default), cache will be queried and updated.
  *      @return int						Return integer <0 if KO, Id of code if OK
  *      @see $langs->getLabelFromKey
  */
-function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0, $filters = '')
+function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0, $filters = '', $useCache = true)
 {
 	global $cache_codes;
 
@@ -9570,8 +9571,8 @@ function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid =
 	}
 
 	// Check in cache
-	if (isset($cache_codes[$tablename][$key][$fieldid])) {	// Can be defined to 0 or ''
-		return $cache_codes[$tablename][$key][$fieldid]; // Found in cache
+	if ($useCache && isset($cache_codes[$tablename][$fieldkey][$fieldid][$entityfilter][$filters][$key])) {	// Can be defined to 0 or ''
+		return $cache_codes[$tablename][$fieldkey][$fieldid][$entityfilter][$filters][$key]; // Found in cache
 	}
 
 	dol_syslog('dol_getIdFromCode (value for field '.$fieldid.' from key '.$key.' not found into cache)', LOG_DEBUG);
@@ -9589,13 +9590,15 @@ function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid =
 	$resql = $db->query($sql);
 	if ($resql) {
 		$obj = $db->fetch_object($resql);
+		$valuetoget = '';
 		if ($obj) {
-			$cache_codes[$tablename][$key][$fieldid] = $obj->valuetoget;
-		} else {
-			$cache_codes[$tablename][$key][$fieldid] = '';
+			$valuetoget = $obj->valuetoget;
 		}
 		$db->free($resql);
-		return $cache_codes[$tablename][$key][$fieldid];
+		if ($useCache) {
+			$cache_codes[$tablename][$fieldkey][$fieldid][$entityfilter][$filters][$key] = $valuetoget;
+		}
+		return $valuetoget;
 	} else {
 		return -1;
 	}
@@ -11588,11 +11591,11 @@ function dolGetStatus($statusLabel = '', $statusLabelShort = '', $html = '', $st
  *                              'classOverride' => '' // to replace class attribute of the button
  *                              ],
  *                              'confirm' => [
- *                              'url' => 'http://', // Overide Url to go when user click on action btn, if empty default url is $url.?confirm=yes, for no js compatibility use $url for fallback confirm.
- *                              'title' => '', // Overide title of modal,  if empty default title use "ConfirmBtnCommonTitle" lang key
- *                              'action-btn-label' => '', // Overide label of action button,  if empty default label use "Confirm" lang key
- *                              'cancel-btn-label' => '', // Overide label of cancel button,  if empty default label use "CloseDialog" lang key
- *                              'content' => '', // Overide text of content,  if empty default content use "ConfirmBtnCommonContent" lang key
+ *                              'url' => 'http://', // Override Url to go when user click on action btn, if empty default url is $url.?confirm=yes, for no js compatibility use $url for fallback confirm.
+ *                              'title' => '', // Override title of modal,  if empty default title use "ConfirmBtnCommonTitle" lang key
+ *                              'action-btn-label' => '', // Override label of action button,  if empty default label use "Confirm" lang key
+ *                              'cancel-btn-label' => '', // Override label of cancel button,  if empty default label use "CloseDialog" lang key
+ *                              'content' => '', // Override text of content,  if empty default content use "ConfirmBtnCommonContent" lang key
  *                              'modal' => true, // true|false to display dialog as a modal (with dark background)
  *                              'isDropDrown' => false, // true|false to display dialog as a dropdown (with dark background)
  *                              ],

+ 48 - 2
htdocs/core/lib/pdf.lib.php

@@ -1485,6 +1485,8 @@ function pdf_getlinedesc($object, $i, $outputlangs, $hideref = 0, $hidedesc = 0,
 	$note = (!empty($object->lines[$i]->note) ? $object->lines[$i]->note : '');
 	$dbatch = (!empty($object->lines[$i]->detail_batch) ? $object->lines[$i]->detail_batch : false);
 
+	$multilangsactive = getDolGlobalInt('MAIN_MULTILANGS');
+
 	if ($issupplierline) {
 		include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
 		$prodser = new ProductFournisseur($db);
@@ -1497,11 +1499,55 @@ function pdf_getlinedesc($object, $i, $outputlangs, $hideref = 0, $hidedesc = 0,
 		}
 	}
 
+	//id
+	$idprod = (!empty($object->lines[$i]->fk_product) ? $object->lines[$i]->fk_product : false);
 	if ($idprod) {
 		$prodser->fetch($idprod);
+		//load multilangs
+		if ($multilangsactive) {
+			$prodser->getMultiLangs();
+			$object->lines[$i]->multilangs = $prodser->multilangs;
+		}
+	}
+	//label
+	if (!empty($object->lines[$i]->label)) {
+		$label = $object->lines[$i]->label;
+	} else {
+		if (!empty($object->lines[$i]->multilangs[$outputlangs->defaultlang]['label']) && $multilangsactive) {
+			$label = $object->lines[$i]->multilangs[$outputlangs->defaultlang]['label'];
+		} else {
+			if (!empty($object->lines[$i]->product_label)) {
+				$label = $object->lines[$i]->product_label;
+			} else {
+				$label = '';
+			}
+		}
+	}
+	//description
+	if (!empty($object->lines[$i]->desc)) {
+		$desc = $object->lines[$i]->desc;
+	} else {
+		if (!empty($object->lines[$i]->multilangs[$outputlangs->defaultlang]['description']) && $multilangsactive) {
+			$desc = $object->lines[$i]->multilangs[$outputlangs->defaultlang]['description'];
+		} else {
+			if (!empty($object->lines[$i]->description)) {
+				$desc = $object->lines[$i]->description;
+			} else {
+				$desc = '';
+			}
+		}
+	}
+	//ref supplier
+	$ref_supplier = (!empty($object->lines[$i]->ref_supplier) ? $object->lines[$i]->ref_supplier : (!empty($object->lines[$i]->ref_fourn) ? $object->lines[$i]->ref_fourn : '')); // TODO Not yet saved for supplier invoices, only supplier orders
+	//note
+	$note = (!empty($object->lines[$i]->note) ? $object->lines[$i]->note : '');
+	//dbatch
+	$dbatch = (!empty($object->lines[$i]->detail_batch) ? $object->lines[$i]->detail_batch : false);
+
+	if ($idprod) {
 		// If a predefined product and multilang and on other lang, we renamed label with label translated
-		if (getDolGlobalInt('MAIN_MULTILANGS') && ($outputlangs->defaultlang != $langs->defaultlang)) {
-			$translatealsoifmodified = (getDolGlobalString('MAIN_MULTILANG_TRANSLATE_EVEN_IF_MODIFIED')); // By default if value was modified manually, we keep it (no translation because we don't have it)
+		if ($multilangsactive && ($outputlangs->defaultlang != $langs->defaultlang)) {
+			$translatealsoifmodified = getDolGlobalString('MAIN_MULTILANG_TRANSLATE_EVEN_IF_MODIFIED'); // By default if value was modified manually, we keep it (no translation because we don't have it)
 
 			// TODO Instead of making a compare to see if param was modified, check that content contains reference translation. If yes, add the added part to the new translation
 			// ($textwasnotmodified is replaced with $textwasmodifiedorcompleted and we add completion).

+ 106 - 26
htdocs/core/triggers/interface_50_modTicket_TicketEmail.class.php

@@ -87,38 +87,17 @@ class InterfaceTicketEmail extends DolibarrTriggers
 								$appli = $mysoc->name;
 
 								// Send email to assigned user
-								$subject = '['.$appli.'] '.$langs->transnoentities('TicketAssignedToYou');
-								$message = '<p>'.$langs->transnoentities('TicketAssignedEmailBody', $object->track_id, dolGetFirstLastname($user->firstname, $user->lastname))."</p>";
-								$message .= '<ul><li>'.$langs->trans('Title').' : '.$object->subject.'</li>';
-								$message .= '<li>'.$langs->trans('Type').' : '.$object->type_label.'</li>';
-								$message .= '<li>'.$langs->trans('Category').' : '.$object->category_label.'</li>';
-								$message .= '<li>'.$langs->trans('Severity').' : '.$object->severity_label.'</li>';
-								// Extrafields
-								if (is_array($object->array_options) && count($object->array_options) > 0) {
-									foreach ($object->array_options as $key => $value) {
-										$message .= '<li>'.$langs->trans($key).' : '.$value.'</li>';
-									}
-								}
-
-								$message .= '</ul>';
-								$message .= '<p>'.$langs->trans('Message').' : <br>'.$object->message.'</p>';
-								$message .= '<p><a href="'.dol_buildpath('/ticket/card.php', 2).'?track_id='.$object->track_id.'">'.$langs->trans('SeeThisTicketIntomanagementInterface').'</a></p>';
-
 								$sendto = $userstat->email;
-								$from = dolGetFirstLastname($user->firstname, $user->lastname).'<'.$user->email.'>';
-
-								$message = dol_nl2br($message);
+								$subject_assignee = 'TicketAssignedToYou';
+								$body_assignee = 'TicketAssignedEmailBody';
+								$see_ticket_assignee = 'SeeThisTicketIntomanagementInterface';
 
 								if (getDolGlobalString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
 									$old_MAIN_MAIL_AUTOCOPY_TO = $conf->global->MAIN_MAIL_AUTOCOPY_TO;
 									$conf->global->MAIN_MAIL_AUTOCOPY_TO = '';
 								}
-								include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
-								$mailfile = new CMailFile($subject, $sendto, $from, $message, $filepath, $mimetype, $filename, '', '', 0, -1);
-								if ($mailfile->error) {
-									setEventMessages($mailfile->error, $mailfile->errors, 'errors');
-								} else {
-									$result = $mailfile->sendfile();
+								if (!empty($sendto)) {
+									$this->composeAndSendAssigneeMessage($sendto, $subject_assignee, $body_assignee, $see_ticket_assignee, $object, $langs);
 								}
 								if (getDolGlobalString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
 									$conf->global->MAIN_MAIL_AUTOCOPY_TO = $old_MAIN_MAIL_AUTOCOPY_TO;
@@ -177,10 +156,15 @@ class InterfaceTicketEmail extends DolibarrTriggers
 
 				$subject_admin = 'TicketNewEmailSubjectAdmin';
 				$body_admin = 'TicketNewEmailBodyAdmin';
+
 				$subject_customer = 'TicketNewEmailSubjectCustomer';
 				$body_customer = 'TicketNewEmailBodyCustomer';
 				$see_ticket_customer = 'TicketNewEmailBodyInfosTrackUrlCustomer';
 
+				$subject_assignee = 'TicketAssignedToYou';
+				$body_assignee = 'TicketAssignedEmailBody';
+				$see_ticket_assignee = 'SeeThisTicketIntomanagementInterface';
+
 				// Send email to notification email
 				if (getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO') && empty($object->context['disableticketemail'])) {
 					$sendto = !getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO') ? '' : $conf->global->TICKET_NOTIFICATION_EMAIL_TO;
@@ -189,6 +173,34 @@ class InterfaceTicketEmail extends DolibarrTriggers
 					}
 				}
 
+				// Send email to assignee if an assignee was set at creation
+				if ($object->fk_user_assign > 0 && $object->fk_user_assign != $user->id && empty($object->context['disableticketemail'])) {
+					$userstat = new User($this->db);
+					$res = $userstat->fetch($object->fk_user_assign);
+					if ($res > 0) {
+						// Send email to notification email
+						if (!getDolGlobalString('TICKET_DISABLE_ALL_MAILS')) {
+							// Send email to assigned user
+							$sendto = $userstat->email;
+							if (!getDolGlobalString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
+								$old_MAIN_MAIL_AUTOCOPY_TO = $conf->global->MAIN_MAIL_AUTOCOPY_TO;
+								$conf->global->MAIN_MAIL_AUTOCOPY_TO = '';
+							}
+
+							if (!empty($sendto)) {
+								$this->composeAndSendAssigneeMessage($sendto, $subject_assignee, $body_assignee, $see_ticket_assignee, $object, $langs);
+							}
+
+							if (!getDolUserString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
+								$conf->global->MAIN_MAIL_AUTOCOPY_TO = $old_MAIN_MAIL_AUTOCOPY_TO;
+							}
+						}
+					} else {
+						$this->error = $userstat->error;
+						$this->errors = $userstat->errors;
+					}
+				}
+
 				// Send email to customer
 				if (!getDolGlobalString('TICKET_DISABLE_CUSTOMER_MAILS') && empty($object->context['disableticketemail']) && $object->notify_tiers_at_create) {
 					$sendto = '';
@@ -460,4 +472,72 @@ class InterfaceTicketEmail extends DolibarrTriggers
 			$conf->global->MAIN_MAIL_AUTOCOPY_TO = $old_MAIN_MAIL_AUTOCOPY_TO;
 		}
 	}
+
+	/**
+	 * Composes and sends a message concerning a ticket, to be sent to user assigned to the ticket
+	 *
+	 * @param string 	$sendto			Addresses to send the mail, format "first@address.net, second@address.net, " etc.
+	 * @param string 	$base_subject	email subject. Non-translated string.
+	 * @param string	$body			email body (first line). Non-translated string.
+	 * @param string 	$see_ticket		string indicating the ticket public address
+	 * @param Ticket 	$object			the ticket thet the email refers to
+	 * @param Translate $langs			the translation object
+	 * @return void
+	 */
+	private function composeAndSendAssigneeMessage($sendto, $base_subject, $body, $see_ticket, Ticket $object, Translate $langs)
+	{
+		global $conf, $user, $mysoc;
+
+		// Init to avoid errors
+		$filepath = array();
+		$filename = array();
+		$mimetype = array();
+
+		// Send email to assigned user
+		$appli = $mysoc->name;
+
+		$subject = '['.$appli.'] '.$langs->transnoentities($base_subject);
+		$message = '<p>'.$langs->transnoentities($body, $object->track_id, dolGetFirstLastname($user->firstname, $user->lastname))."</p>";
+		$message .= '<ul><li>'.$langs->trans('Title').' : '.$object->subject.'</li>';
+		$message .= '<li>'.$langs->trans('Type').' : '.$object->type_label.'</li>';
+		$message .= '<li>'.$langs->trans('Category').' : '.$object->category_label.'</li>';
+		$message .= '<li>'.$langs->trans('Severity').' : '.$object->severity_label.'</li>';
+		// Extrafields
+		if (is_array($object->array_options) && count($object->array_options) > 0) {
+			foreach ($object->array_options as $key => $value) {
+				$message .= '<li>'.$langs->trans($key).' : '.$value.'</li>';
+			}
+		}
+
+		$message .= '</ul>';
+		$message .= '<p>'.$langs->trans('Message').' : <br>'.$object->message.'</p>';
+		$message .= '<p><a href="'.dol_buildpath('/ticket/card.php', 2).'?track_id='.$object->track_id.'">'.$langs->trans($see_ticket).'</a></p>';
+
+		$from = dolGetFirstLastname($user->firstname, $user->lastname).'<'.$user->email.'>';
+
+		$message = dol_nl2br($message);
+
+		$old_MAIN_MAIL_AUTOCOPY_TO = null;
+		if (getDolGlobalString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
+			$old_MAIN_MAIL_AUTOCOPY_TO = getDolGlobalString('MAIN_MAIL_AUTOCOPY_TO');
+			$conf->global->MAIN_MAIL_AUTOCOPY_TO = '';
+		}
+
+		include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
+		$mailfile = new CMailFile($subject, $sendto, $from, $message, $filepath, $mimetype, $filename, '', '', 0, -1);
+		if ($mailfile->error) {
+			setEventMessages($mailfile->error, $mailfile->errors, 'errors');
+		} else {
+			$result = $mailfile->sendfile();
+			if ($result) {
+				// update last_msg_sent date
+				$object->fetch($object->id);
+				$object->date_last_msg_sent = dol_now();
+				$object->update($user);
+			}
+		}
+		if (!getDolUserString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
+			$conf->global->MAIN_MAIL_AUTOCOPY_TO = $old_MAIN_MAIL_AUTOCOPY_TO;
+		}
+	}
 }

+ 7 - 1
htdocs/don/class/api_donations.class.php

@@ -240,7 +240,13 @@ class Donations extends DolibarrApi
 				continue;
 			}
 
-			$this->don->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->don->array_options[$index] = $this->_checkValForAPI($field, $val, $this->don);
+				}
+				continue;
+			}
+			$this->don->$field = $this->_checkValForAPI($field, $value, $this->don);
 		}
 
 		if ($this->don->update(DolibarrApiAccess::$user) > 0) {

+ 7 - 1
htdocs/expedition/class/api_shipments.class.php

@@ -457,7 +457,13 @@ class Shipments extends DolibarrApi
 				continue;
 			}
 
-			$this->shipment->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->shipment->array_options[$index] = $this->_checkValForAPI($field, $val, $this->shipment);
+				}
+				continue;
+			}
+			$this->shipment->$field = $this->_checkValForAPI($field, $val, $this->shipment);;
 		}
 
 		if ($this->shipment->update(DolibarrApiAccess::$user) > 0) {

+ 7 - 1
htdocs/expensereport/class/api_expensereports.class.php

@@ -430,7 +430,13 @@ class ExpenseReports extends DolibarrApi
 				continue;
 			}
 
-			$this->expensereport->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->expensereport->array_options[$index] = $this->_checkValForAPI($field, $val, $this->expensereport);
+				}
+				continue;
+			}
+			$this->expensereport->$field = $this->_checkValForAPI($field, $value, $this->expensereport);
 		}
 
 		if ($this->expensereport->update(DolibarrApiAccess::$user) > 0) {

+ 1 - 0
htdocs/expensereport/class/expensereport.class.php

@@ -630,6 +630,7 @@ class ExpenseReport extends CommonObject
 		$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as d";
 		if ($ref) {
 			$sql .= " WHERE d.ref = '".$this->db->escape($ref)."'";
+			$sql .= " AND d.entity IN (".getEntity('expensereport').")";
 		} else {
 			$sql .= " WHERE d.rowid = ".((int) $id);
 		}

+ 7 - 0
htdocs/fourn/class/api_supplier_invoices.class.php

@@ -274,6 +274,13 @@ class SupplierInvoices extends DolibarrApi
 				continue;
 			}
 
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->invoice->array_options[$index] = $this->_checkValForAPI($field, $val, $this->invoice);
+				}
+				continue;
+			}
+
 			$this->invoice->$field = $this->_checkValForAPI($field, $value, $this->invoice);
 		}
 

+ 7 - 0
htdocs/fourn/class/api_supplier_orders.class.php

@@ -294,6 +294,13 @@ class SupplierOrders extends DolibarrApi
 				continue;
 			}
 
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->order->array_options[$index] = $this->_checkValForAPI($field, $val, $this->order);
+				}
+				continue;
+			}
+
 			$this->order->$field = $this->_checkValForAPI($field, $value, $this->order);
 		}
 

+ 2 - 2
htdocs/includes/odtphp/odf.php

@@ -53,8 +53,8 @@ class Odf
 	public $userdefined=array();
 
 	const PIXEL_TO_CM = 0.026458333;
-	const FIND_TAGS_REGEX = '/<([A-Za-z0-9]+)(?:\s([A-Za-z]+(?:\-[A-Za-z]+)?(?:=(?:".*?")|(?:[0-9]+))))*(?:(?:\s\/>)|(?:>(.*)<\/\1>))/s';
-	const FIND_ENCODED_TAGS_REGEX = '/&lt;([A-Za-z]+)(?:\s([A-Za-z]+(?:\-[A-Za-z]+)?(?:=(?:".*?")|(?:[0-9]+))))*(?:(?:\s\/&gt;)|(?:&gt;(.*)&lt;\/\1&gt;))/';
+	const FIND_TAGS_REGEX = '/<([A-Za-z0-9]+)(?:\s([A-Za-z]+(?:\-[A-Za-z]+)?(?:=(?:".*?")|(?:[0-9]+))))*(?:(?:\s\/>)|(?:>(((?!<\1(\s.*)?>).)*)<\/\1>))/s';
+	const FIND_ENCODED_TAGS_REGEX = '/&lt;([A-Za-z]+)(?:\s([A-Za-z]+(?:\-[A-Za-z]+)?(?:=(?:".*?")|(?:[0-9]+))))*(?:(?:\s\/&gt;)|(?:&gt;(((?!&lt;\1(\s.*)?&gt;).)*)&lt;\/\1&gt;))/';
 
 
 	/**

+ 7 - 0
htdocs/knowledgemanagement/class/api_knowledgemanagement.class.php

@@ -305,6 +305,13 @@ class KnowledgeManagement extends DolibarrApi
 				continue;
 			}
 
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->knowledgerecord->array_options[$index] = $this->_checkValForAPI($field, $val, $this->knowledgerecord);
+				}
+				continue;
+			}
+
 			$this->knowledgerecord->$field = $this->_checkValForAPI($field, $value, $this->knowledgerecord);
 		}
 

+ 2 - 2
htdocs/loan/card.php

@@ -84,7 +84,7 @@ if (empty($reshook)) {
 		if ($result > 0) {
 			setEventMessages($langs->trans('LoanPaid'), null, 'mesgs');
 		} else {
-			setEventMessages($loan->error, null, 'errors');
+			setEventMessages($object->error, $object->errors, 'errors');
 		}
 	}
 
@@ -97,7 +97,7 @@ if (empty($reshook)) {
 			header("Location: list.php");
 			exit;
 		} else {
-			setEventMessages($loan->error, null, 'errors');
+			setEventMessages($object->error, $object->errors, 'errors');
 		}
 	}
 

+ 1 - 0
htdocs/loan/class/loan.class.php

@@ -308,6 +308,7 @@ class Loan extends CommonObject
 				$accountline->fetch($line_url['fk_bank']);
 				$result = $accountline->delete_urls($user);
 				if ($result < 0) {
+					$this->errors = array_merge($this->errors, [$accountline->error], $accountline->errors);
 					$error++;
 				}
 			}

+ 1 - 1
htdocs/loan/class/paymentloan.class.php

@@ -1,6 +1,6 @@
 <?php
 /* Copyright (C) 2014-2025	Alexandre Spangaro			<alexandre@inovea-conseil.com>
- * Copyright (C) 2015-2023	Frederic France				<frederic.france@netlogic.fr>
+ * Copyright (C) 2015-2023	Frederic France				<frederic.france@free.fr>
  * Copyright (C) 2020		Maxime DEMAREST				<maxime@indelog.fr>
  *
  * This program is free software; you can redistribute it and/or modify

+ 7 - 0
htdocs/modulebuilder/template/class/api_mymodule.class.php

@@ -268,6 +268,13 @@ class MyModuleApi extends DolibarrApi
 				continue;
 			}
 
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->myobject->array_options[$index] = $this->_checkValForAPI($field, $val, $this->myobject);
+				}
+				continue;
+			}
+
 			$this->myobject->$field = $this->_checkValForAPI($field, $value, $this->myobject);
 		}
 

+ 8 - 1
htdocs/mrp/class/api_mos.class.php

@@ -249,7 +249,14 @@ class Mos extends DolibarrApi
 				continue;
 			}
 
-			$this->mo->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->mo->array_options[$index] = $this->_checkValForAPI($field, $val, $this->mo);
+				}
+				continue;
+			}
+
+			$this->mo->$field = $this->_checkValForAPI($field, $value, $this->mo);
 		}
 
 		$this->checkRefNumbering();

+ 6 - 0
htdocs/partnership/class/api_partnerships.class.php

@@ -264,6 +264,12 @@ class Partnerships extends DolibarrApi
 				$this->partnership->context['caller'] = $request_data['caller'];
 				continue;
 			}
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->partnership->array_options[$index] = $this->_checkValForAPI($field, $val, $this->partnership);
+				}
+				continue;
+			}
 
 			$this->partnership->$field = $this->_checkValForAPI($field, $value, $this->partnership);
 		}

+ 8 - 1
htdocs/product/class/api_products.class.php

@@ -371,7 +371,14 @@ class Products extends DolibarrApi
 				continue;
 			}
 
-			$this->product->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->product->array_options[$index] = $this->_checkValForAPI($field, $val, $this->product);
+				}
+				continue;
+			}
+
+			$this->product->$field = $this->_checkValForAPI($field, $value, $this->product);
 		}
 
 		$updatetype = false;

+ 7 - 1
htdocs/product/stock/class/api_warehouses.class.php

@@ -217,7 +217,13 @@ class Warehouses extends DolibarrApi
 				continue;
 			}
 
-			$this->warehouse->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->warehouse->array_options[$index] = $this->_checkValForAPI($field, $val, $this->warehouse);
+				}
+				continue;
+			}
+			$this->warehouse->$field = $this->_checkValForAPI($field, $value, $this->warehouse);
 		}
 
 		if ($this->warehouse->update($id, DolibarrApiAccess::$user)) {

+ 7 - 1
htdocs/projet/class/api_projects.class.php

@@ -476,7 +476,13 @@ class Projects extends DolibarrApi
 				continue;
 			}
 
-			$this->project->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->project->array_options[$index] = $this->_checkValForAPI($field, $val, $this->project);
+				}
+				continue;
+			}
+			$this->project->$field = $this->_checkValForAPI($field, $value, $this->project);
 		}
 
 		if ($this->project->update(DolibarrApiAccess::$user) >= 0) {

+ 7 - 1
htdocs/projet/class/api_tasks.class.php

@@ -472,7 +472,13 @@ class Tasks extends DolibarrApi
 				continue;
 			}
 
-			$this->task->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->task->array_options[$index] = $this->_checkValForAPI($field, $val, $this->task);
+				}
+				continue;
+			}
+			$this->task->$field = $this->_checkValForAPI($field, $val, $this->task);
 		}
 
 		if ($this->task->update(DolibarrApiAccess::$user) > 0) {

+ 7 - 1
htdocs/reception/class/api_receptions.class.php

@@ -458,7 +458,13 @@ class Receptions extends DolibarrApi
 				continue;
 			}
 
-			$this->reception->$field = $value;
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->reception->array_options[$index] = $this->_checkValForAPI($field, $val, $this->reception);
+				}
+				continue;
+			}
+			$this->reception->$field = $this->_checkValForAPI($field, $val, $this->reception);
 		}
 
 		if ($this->reception->update(DolibarrApiAccess::$user) > 0) {

+ 1 - 1
htdocs/societe/class/api_contacts.class.php

@@ -350,7 +350,7 @@ class Contacts extends DolibarrApi
 			}
 			if ($field == 'array_options' && is_array($value)) {
 				foreach ($value as $index => $val) {
-					$this->contact->array_options[$index] = $val;
+					$this->contact->array_options[$index] = $this->_checkValForAPI($field, $val, $this->contact);
 				}
 				continue;
 			}

+ 6 - 0
htdocs/societe/class/api_thirdparties.class.php

@@ -318,6 +318,12 @@ class Thirdparties extends DolibarrApi
 				continue;
 			}
 
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->company->array_options[$index] = $this->_checkValForAPI($field, $val, $this->company);
+				}
+				continue;
+			}
 			$this->company->$field = $this->_checkValForAPI($field, $value, $this->company);
 		}
 

+ 1 - 4
htdocs/societe/list.php

@@ -557,7 +557,7 @@ if ($search_sale == -2) {
 	$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid";
 	//elseif ($search_sale || (empty($user->rights->societe->client->voir) && (empty($conf->global->MAIN_USE_ADVANCED_PERMS) || empty($user->rights->societe->client->readallthirdparties_advance)) && !$socid)) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
 } elseif (!empty($search_sale) && $search_sale != '-1' || (!$user->hasRight('societe', 'client', 'voir') && !$socid)) {
-	$sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
+	$sql .= " INNER JOIN ".MAIN_DB_PREFIX."societe_commerciaux AS sc ON s.rowid = sc.fk_soc";
 }
 // Add table from hooks
 $parameters = array();
@@ -568,9 +568,6 @@ $sql .= " WHERE s.entity IN (".getEntity('societe').")";
 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
 	$sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
 }
-if ($search_sale && $search_sale != '-1' && $search_sale != '-2') {
-	$sql .= " AND s.rowid = sc.fk_soc"; // Join for the needed table to filter by sale
-}
 if (!$user->hasRight('fournisseur', 'lire')) {
 	$sql .= " AND (s.fournisseur <> 1 OR s.client <> 0)"; // client=0, fournisseur=0 must be visible
 }

+ 10 - 1
htdocs/ticket/class/api_tickets.class.php

@@ -394,7 +394,16 @@ class Tickets extends DolibarrApi
 				continue;
 			}
 
-			$this->ticket->$field = $value;
+			if ($field == 'id') {
+				continue;
+			}
+			if ($field == 'array_options' && is_array($value)) {
+				foreach ($value as $index => $val) {
+					$this->ticket->array_options[$index] = $this->_checkValForAPI($field, $val, $this->ticket);
+				}
+				continue;
+			}
+			$this->ticket->$field = $this->_checkValForAPI($field, $value, $this->ticket);
 		}
 
 		if ($this->ticket->update(DolibarrApiAccess::$user)) {

+ 7 - 1
htdocs/user/class/api_users.class.php

@@ -417,7 +417,13 @@ class Users extends DolibarrApi
 					throw new RestException(500, 'Error when updating status of user: '.$this->useraccount->error);
 				}
 			} else {
-				$this->useraccount->$field = $value;
+				if ($field == 'array_options' && is_array($value)) {
+					foreach ($value as $index => $val) {
+						$this->useraccount->array_options[$index] = $this->_checkValForAPI($field, $val, $this->useraccount);
+					}
+					continue;
+				}
+				$this->useraccount->$field = $this->_checkValForAPI($field, $value, $this->useraccount);
 			}
 		}
 

+ 27 - 14
htdocs/variants/class/ProductCombination.class.php

@@ -702,16 +702,16 @@ class ProductCombination
 	 * [...]
 	 * )
 	 *
-	 * @param User 			$user 				Object user
-	 * @param Product 		$product 			Parent product
-	 * @param array 		$combinations 		Attribute and value combinations.
-	 * @param array 		$variations 		Price and weight variations
-	 * @param bool|array 	$price_var_percent 	Is the price variation a relative variation?
-	 * @param bool|float 	$forced_pricevar 	If the price variation is forced
-	 * @param bool|float 	$forced_weightvar 	If the weight variation is forced
-	 * @param bool|string 	$forced_refvar 		If the reference is forced
-	 * @param string 	    $ref_ext            External reference
-	 * @return int 								Return integer <0 KO, >0 OK
+	 * @param User 				$user 				Object user
+	 * @param Product 			$product 			Parent product
+	 * @param array 			$combinations 		Attribute and value combinations.
+	 * @param array 			$variations 		Price and weight variations
+	 * @param bool|array		$price_var_percent 	Is the price variation a relative variation?
+	 * @param bool|float|array	$forced_pricevar 	If the price variation is forced
+	 * @param bool|float 		$forced_weightvar 	If the weight variation is forced
+	 * @param bool|string 		$forced_refvar 		If the reference is forced
+	 * @param string 	    	$ref_ext            External reference
+	 * @return int 									Return integer <0 KO, >0 OK
 	 */
 	public function createProductCombination(User $user, Product $product, array $combinations, array $variations, $price_var_percent = false, $forced_pricevar = false, $forced_weightvar = false, $forced_refvar = false, $ref_ext = '')
 	{
@@ -752,8 +752,8 @@ class ProductCombination
 			$price_impact = $forced_pricevar;
 		}
 
-		if (!array($price_var_percent)) {
-			$price_var_percent[1] = (float) $price_var_percent;
+		if (!is_array($price_var_percent)) {
+			$price_var_percent = array(1 => (bool) $price_var_percent);
 		}
 
 		$newcomb = new ProductCombination($this->db);
@@ -955,13 +955,26 @@ class ProductCombination
 				$variations[$tmp_pc2v->fk_prod_attr] = $tmp_pc2v->fk_prod_attr_val;
 			}
 
+			$variation_price_percentage = $combination->variation_price_percentage;
+			$variation_price = $combination->variation_price;
+
+			if (getDolGlobalInt('PRODUIT_MULTIPRICES') && getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT') > 1) {
+				$variation_price_percentage = [ ];
+				$variation_price = [ ];
+
+				foreach ($combination->combination_price_levels as $productCombinationLevel) {
+					$variation_price_percentage[$productCombinationLevel->fk_price_level] = $productCombinationLevel->variation_price_percentage;
+					$variation_price[$productCombinationLevel->fk_price_level] = $productCombinationLevel->variation_price;
+				}
+			}
+
 			if ($this->createProductCombination(
 				$user,
 				$destProduct,
 				$variations,
 				array(),
-				$combination->variation_price_percentage,
-				$combination->variation_price,
+				$variation_price_percentage,
+				$variation_price,
 				$combination->variation_weight
 			) < 0) {
 				return -1;

+ 21 - 9
test/phpunit/ODFTest.php

@@ -296,33 +296,45 @@ class ODFTest extends PHPUnit\Framework\TestCase
 				'charset' => null,
 				'expected' => mb_convert_encoding('text with <text:span text:style-name="boldText">intricated<text:span text:style-name="underlineText">tags</text:span></text:span>', 'UTF-8', 'ISO-8859-1'),
 			],
+			24 => [
+				'to_convert' => "text with <strong>two</strong> (strong) <strong>tags</strong>",
+				'encode' => true,
+				'charset' => null,
+				'expected' => utf8_encode('text with <text:span text:style-name="boldText">two</text:span> (strong) <text:span text:style-name="boldText">tags</text:span>'),
+			],
+			25 => [
+				'to_convert' => "text with <strong class=\"whatever\">two</strong> (strong) <strong class=\"the weather\">tags and <u>intricated</u> underline </strong>",
+				'encode' => true,
+				'charset' => null,
+				'expected' => utf8_encode('text with <text:span text:style-name="boldText">two</text:span> (strong) <text:span text:style-name="boldText">tags and <text:span text:style-name="underlineText">intricated</text:span> underline </text:span>'),
+			],
 
 			// One can also pass html-encoded string to the method
-			24 => [
+			26 => [
 				'to_convert' => 'One&amp;two',
 				'encode' => true,
 				'charset' => null,
 				'expected' => 'One&amp;two',
 			],
-			25 => [
+			27 => [
 				'to_convert' => "text with &lt;strong&gt;strong, &lt;/strong&gt;&lt;em&gt;emphasis&lt;/em&gt; and &lt;u&gt;underlined&lt;/u&gt; words with &lt;i&gt;it@lic sp&amp;ciàlchärs éè l'&lt;/i&gt;",
 				'encode' => false,
 				'charset' => 'UTF-8',
 				'expected' => 'text with <text:span text:style-name="boldText">strong, </text:span><text:span text:style-name="italicText">emphasis</text:span> and <text:span text:style-name="underlineText">underlined</text:span> words with <text:span text:style-name="italicText">it@lic sp&ciàlchärs éè l\'</text:span>',
 			],
-			26 => [
+			28 => [
 				'to_convert' => "text with &lt;strong&gt;strong, &lt;/strong&gt;&lt;em&gt;emphasis&lt;/em&gt; and &lt;u&gt;underlined&lt;/u&gt; words with &lt;i&gt;it@lic sp&amp;ciàlchärs éè l'&lt;/i&gt;",
 				'encode' => true,
 				'charset' => 'UTF-8',
 				'expected' => 'text with <text:span text:style-name="boldText">strong, </text:span><text:span text:style-name="italicText">emphasis</text:span> and <text:span text:style-name="underlineText">underlined</text:span> words with <text:span text:style-name="italicText">it@lic sp&amp;ciàlchärs éè l&apos;</text:span>',
 			],
-			27 => [
+			29 => [
 				'to_convert' => "text with &lt;strong&gt;strong, &lt;/strong&gt;&lt;em&gt;emphasis&lt;/em&gt; and &lt;u&gt;underlined&lt;/u&gt; words with &lt;i&gt;it@lic sp&amp;ciàlchärs éè l'&lt;/i&gt;",
 				'encode' => false,
 				'charset' => null,
 				'expected' => mb_convert_encoding('text with <text:span text:style-name="boldText">strong, </text:span><text:span text:style-name="italicText">emphasis</text:span> and <text:span text:style-name="underlineText">underlined</text:span> words with <text:span text:style-name="italicText">it@lic sp&ciàlchärs éè l\'</text:span>', 'UTF-8', 'ISO-8859-1'),
 			],
-			28 => [
+			30 => [
 				'to_convert' => "text with &lt;strong&gt;strong, &lt;/strong&gt;&lt;em&gt;emphasis&lt;/em&gt; and &lt;u&gt;underlined&lt;/u&gt; words with &lt;i&gt;it@lic sp&amp;ciàlchärs éè l'&lt;/i&gt;",
 				'encode' => true,
 				'charset' => null,
@@ -341,20 +353,20 @@ class ODFTest extends PHPUnit\Framework\TestCase
 			// Following tests reflect the current behavior. They may evolve if the method behavior changes.
 
 			// The method removes hyperlinks and tags that are not dealt with.
-			29 => [
+			31 => [
 				'to_convert' => '123 <a href="/test.php">trucmachin > truc < troc > trac</a>bla bla',
 				'encode' => true,
 				'charset' => null,
 				'expected' => "123 trucmachin &gt; truc &lt; troc &gt; tracbla bla",
 			],
-			30 => [
+			32 => [
 				'to_convert' => '123 <h3>Title</h3> bla',
 				'encode' => true,
 				'charset' => null,
 				'expected' => "123 Title bla",
 			],
 			// HTML should not take \n into account, but only <br />.
-			31 => [
+			33 => [
 				'to_convert' => "text with <strong>strong text </strong>, a line\nbreak and <u>underlined</u> words with <i>it@lic sp&ciàlchärs éè l'</i>",
 				'encode' => false,
 				'charset' => 'UTF-8',
@@ -373,7 +385,7 @@ class ODFTest extends PHPUnit\Framework\TestCase
 			} else {
 				$res = $odf->convertVarToOdf($case['to_convert'], $case['encode']);
 			}
-			$this->assertEquals($res, $case['expected']);
+			$this->assertEquals($case['expected'], $res);
 		}
 
 		print __METHOD__." result=".$result."\n";