Explorar o código

Merge branch '16.0' into 16.0-mmi

Mathieu Moulin %!s(int64=2) %!d(string=hai) anos
pai
achega
ddd9d1a263
Modificáronse 54 ficheiros con 301 adicións e 92 borrados
  1. 45 1
      ChangeLog
  2. 1 1
      build/exe/doliwamp/Languages/MyCatalan.isl
  3. 1 1
      build/exe/doliwamp/Languages/MyEnglish.isl
  4. 1 1
      build/exe/doliwamp/Languages/MyFrench.isl
  5. 1 1
      build/exe/doliwamp/Languages/MyGerman.isl
  6. 1 1
      build/exe/doliwamp/Languages/MySpanish.isl
  7. 3 2
      build/generate_filelist_xml.php
  8. 4 1
      htdocs/accountancy/admin/fiscalyear.php
  9. 1 1
      htdocs/accountancy/journal/sellsjournal.php
  10. 7 1
      htdocs/bookmarks/bookmarks.lib.php
  11. 1 1
      htdocs/categories/class/categorie.class.php
  12. 2 2
      htdocs/comm/action/card.php
  13. 1 1
      htdocs/comm/action/class/actioncomm.class.php
  14. 1 0
      htdocs/comm/action/list.php
  15. 1 1
      htdocs/comm/propal/card.php
  16. 3 0
      htdocs/commande/class/commande.class.php
  17. 2 0
      htdocs/commande/list.php
  18. 12 2
      htdocs/compta/prelevement/line.php
  19. 1 1
      htdocs/contact/consumption.php
  20. 1 1
      htdocs/core/actions_massactions.inc.php
  21. 3 3
      htdocs/core/boxes/box_graph_product_distribution.php
  22. 9 3
      htdocs/core/class/commonobject.class.php
  23. 3 0
      htdocs/core/class/extrafields.class.php
  24. 5 1
      htdocs/core/class/html.form.class.php
  25. 14 8
      htdocs/core/lib/files.lib.php
  26. 22 0
      htdocs/core/lib/images.lib.php
  27. 2 2
      htdocs/core/lib/project.lib.php
  28. 8 4
      htdocs/core/lib/website.lib.php
  29. 3 2
      htdocs/core/menus/init_menu_auguria.sql
  30. 36 7
      htdocs/core/modules/expedition/doc/pdf_espadon.modules.php
  31. 2 2
      htdocs/core/modules/project/doc/pdf_baleine.modules.php
  32. 2 2
      htdocs/core/modules/project/doc/pdf_beluga.modules.php
  33. 2 2
      htdocs/core/modules/project/doc/pdf_timespent.modules.php
  34. 22 3
      htdocs/expedition/card.php
  35. 16 4
      htdocs/expensereport/card.php
  36. 4 4
      htdocs/imports/import.php
  37. 0 0
      htdocs/install/doctemplates/index.html
  38. 0 0
      htdocs/install/doctemplates/websites/index.html
  39. 2 0
      htdocs/install/mysql/migration/15.0.0-16.0.0.sql
  40. 13 8
      htdocs/install/mysql/migration/repair.sql
  41. 1 1
      htdocs/install/mysql/tables/llx_bank.sql
  42. 1 1
      htdocs/install/mysql/tables/llx_overwrite_trans.key.sql
  43. 4 4
      htdocs/projet/card.php
  44. 1 0
      htdocs/projet/class/project.class.php
  45. 3 1
      htdocs/projet/class/task.class.php
  46. 2 2
      htdocs/projet/document.php
  47. 1 0
      htdocs/projet/tasks.php
  48. 1 1
      htdocs/projet/tasks/document.php
  49. 4 3
      htdocs/projet/tasks/time.php
  50. 0 3
      htdocs/public/ticket/create_ticket.php
  51. 1 1
      htdocs/reception/card.php
  52. 1 0
      htdocs/user/list.php
  53. 5 0
      test/phpunit/AllTests.php
  54. 18 0
      test/phpunit/WebsiteTest.php

+ 45 - 1
ChangeLog

@@ -5,7 +5,51 @@ English Dolibarr ChangeLog
 
 ***** ChangeLog for 16.0.5 compared to 16.0.4 *****
 
-TODO
+FIX: 16.0 propalestats Unknown column 'p.fk_soc' in 'on clause'
+FIX: #23804
+FIX: #23860
+FIX: #23966 Error "Param dbt_keyfield is required but not defined"
+FIX: accountancy lettering: better error management
+FIX: accountancy lettering: correctly calculated number of lettering operations done
+FIX: accountancy lettering: error management and prevention
+FIX: accountancy lettering: prevent null results when fetching link with payments
+FIX: Add missing hook on LibStatut
+FIX: Add more context for selectForFormsListWhere Hook
+FIX: attach file and send by mail in ticket
+FIX: bad check on if in get_all_ways
+FIX: Cannot import find type_fees with cgenericdic.class because it has id and not rowid
+FIX: clicktodial backtopage
+FIX: discount wasn't taken into account when adding a line in BOM
+FIX: expense reports: error when selecting mileage fees expense type if MAIN_USE_EXPENSE_IK disabled
+FIX: expense reports: JS error when selecting mileage fees expense type if MAIN_USE_EXPENSE_IK disabled
+FIX: Extrafields in Notes to unify with orders or invoices.
+FIX: fatal error on clicktodial backtopage
+FIX: filter sql accounting account
+FIX: Get data back on product update
+FIX: Get data back when error on command create
+FIX: label dictionary is used by barcode and member module
+FIX: mandatory date for service didnt work for invoice
+FIX: missing "authorid" for getNomUrl link right access
+FIX: missing getEntity filter
+FIX: vulnerability: missing protection on ajax public ticket page for valid email.
+FIX: Missing right to edit service note when module product is disabled
+FIX: multicompany compatibility
+FIX: object $user is not defined
+FIX: Object of class LDAP\Connection could not be converted to string
+FIX: parse error and NAN
+FIX: product ref fourn same size in supplier order/invoice as in product price fourn
+FIX: Profit calculation on project preview tab.
+FIX: Remove orphelan $this->db->rollback() in the function insertExtrafields()
+FIX: request new password with "mc" and "twofactor" authentication
+FIX: Resolve error message due to missing arguments
+FIX: select for task in event card
+FIX: several email sent to the same recipient when adding message from ticket
+FIX: shipping list for external user
+FIX: SQL error "unknown column p.fk_soc" because ANSI-92 joins take precedence over ANSI-89 joins
+FIX: strato pdf
+FIX: typos in getAttchments() $arrayobject
+FIX: whitespaces
+FIX: wrong url param name action
 
 
 ***** ChangeLog for 16.0.4 compared to 16.0.3 *****

+ 1 - 1
build/exe/doliwamp/Languages/MyCatalan.isl

@@ -43,5 +43,5 @@ DoliWampWillStartApacheMysql=L'instal·lador DoliWamp intentarà iniciar o reini
 OldVersionFoundAndMoveInNew=S'ha trobat una versió antiga de base de dades i ha estat moguda per a ser utilitzada per la nova versió de Dolibarr
 OldVersionFoundButFailedToMoveInNew=S'ha trobat una versió antiga de base de dades, però no es pot moure per a ser utilitzada per la nova versió de Dolibarr
 
-DLLMissing=La teva instal·lació windows no té el component "Microsoft Visual C++ Redistributable for Visual Studio 2012". Instal·la primer la versió de 32-bit (vcredist_x86.exe) (pots trobar-la a https://www.microsoft.com/en-us/download/) i reiniciar després la instal·lació/actualització de DoliWamp.
+DLLMissing=La teva instal·lació windows no té el component "Microsoft Visual C++ Redistributable for Visual Studio 2015". Instal·la primer la versió de 32-bit (vcredist_x86.exe) (pots trobar-la a https://www.microsoft.com/en-us/download/) i reiniciar després la instal·lació/actualització de DoliWamp.
 ContinueAnyway=Continua igualment (el procés d'instal·lació podria fallar sense aquest prerequisit)

+ 1 - 1
build/exe/doliwamp/Languages/MyEnglish.isl

@@ -44,5 +44,5 @@ DoliWampWillStartApacheMysql=DoliWamp installer will now start or restart Apache
 OldVersionFoundAndMoveInNew=An old database version has been found and moved to be used by new Dolibarr version
 OldVersionFoundButFailedToMoveInNew=An old database version has been found but could not be moved to be used with new Dolibarr version
 
-DLLMissing=Your Windows installation is missing The "Micrsoft Visual C++ Redistributable for Visual Studio 2012" component. Please install the 32-bit version (vcredist_x86.exe) first (you can find it at https://www.microsoft.com/en-us/download/) and restart DoliWamp installation/upgrade after.
+DLLMissing=Your Windows installation is missing The "Micrsoft Visual C++ Redistributable for Visual Studio 2015" component. Please install the 32-bit version (vcredist_x86.exe) first (you can find it at https://www.microsoft.com/en-us/download/) and restart DoliWamp installation/upgrade after.
 ContinueAnyway=Continue anyway (install process may fails without this prerequisite)

+ 1 - 1
build/exe/doliwamp/Languages/MyFrench.isl

@@ -44,5 +44,5 @@ DoliWampWillStartApacheMysql=L'installeur DoliWamp va maintenant d
 OldVersionFoundAndMoveInNew=Une ancienne version de base a été trouvée et déplacée pour fonctionner avec la nouvelle version de Dolibarr.
 OldVersionFoundButFailedToMoveInNew=Une ancienne version de base a été trouvée mais ne peut être déplacée pour être utilisée avec la nouvelle version de Dolibarr.
 
-DLLMissing=L'installation de votre Windows est incomplète. Il manque le composant "Micrsoft Visual C++ Redistributable for Visual Studio 2012". Installer la version 32-bit (vcredist_x86.exe) d'abord (vous pourrez le trouver à https://www.microsoft.com/fr-fr/download/) puis relancer l'installation de DoliWamp après.
+DLLMissing=L'installation de votre Windows est incomplète. Il manque le composant "Micrsoft Visual C++ Redistributable for Visual Studio 2015". Installer la version 32-bit (vcredist_x86.exe) d'abord (vous pourrez le trouver à https://www.microsoft.com/fr-fr/download/) puis relancer l'installation de DoliWamp après.
 ContinueAnyway=Continuer malgré tout (le process d'installaton échouera)

+ 1 - 1
build/exe/doliwamp/Languages/MyGerman.isl

@@ -43,5 +43,5 @@ DoliWampWillStartApacheMysql=Die DoliWamp-Installation wird nun starten oder Apa
 OldVersionFoundAndMoveInNew=Eine alte Datenbankversion wurde gefunden und verschoben, um von der neuen Dolibarr-Version verwendet zu werden.
 OldVersionFoundButFailedToMoveInNew=Eine alte Datenbankversion wurde gefunden, konnte jedoch nicht verschoben werden, um mit der neuen Dolibarr-Version verwendet zu werden.
 
-DLLMissing=Your Windows installation is missing The "Micrsoft Visual C++ Redistributable for Visual Studio 2012" component. Please install the 32-bit version (vcredist_x86.exe) first (you can find it at https://www.microsoft.com/en-us/download/) and restart DoliWamp installation/upgrade after.
+DLLMissing=Your Windows installation is missing The "Micrsoft Visual C++ Redistributable for Visual Studio 2015" component. Please install the 32-bit version (vcredist_x86.exe) first (you can find it at https://www.microsoft.com/en-us/download/) and restart DoliWamp installation/upgrade after.
 ContinueAnyway=Fahren Sie trotzdem fort (der Installationsvorgang kann ohne diese Voraussetzung fehlschlagen).

+ 1 - 1
build/exe/doliwamp/Languages/MySpanish.isl

@@ -43,5 +43,5 @@ DoliWampWillStartApacheMysql=El instalador DoliWamp intentará iniciar o reinici
 OldVersionFoundAndMoveInNew=Se ha encontrado una versión antigua de base de datos y ha sido movida para ser utilizada por la nueva versión de Dolibarr
 OldVersionFoundButFailedToMoveInNew=Se ha encontrado una versión antigua de base de datos, pero no se pudo mover para ser utilizada por la nueva versión de Dolibarr
  	  	 
-DLLMissing=Su instalación Windows no tiene el componente "Microsoft Visual C++ Redistributable for Visual Studio 2012". Instale primero la versión de 32-bit (vcredist_x86.exe) (puedes encontrarlo en https://www.microsoft.com/en-us/download/) y reiniciar después la instalación/actualización de DoliWamp.
+DLLMissing=Su instalación Windows no tiene el componente "Microsoft Visual C++ Redistributable for Visual Studio 2015". Instale primero la versión de 32-bit (vcredist_x86.exe) (puedes encontrarlo en https://www.microsoft.com/en-us/download/) y reiniciar después la instalación/actualización de DoliWamp.
 ContinueAnyway=Continua igualmente (el proceso de instalación podría fallar sin este prerequisito)

+ 3 - 2
build/generate_filelist_xml.php

@@ -55,9 +55,10 @@ if (empty($argv[1])) {
 
 
 $i=0;
+$result = array();
 while ($i < $argc) {
-	if (! empty($argv[$i])) {
-		parse_str($argv[$i]);	// set all params $release, $includecustom, $includeconstant, $buildzip ...
+	if (!empty($argv[$i])) {
+		parse_str($argv[$i], $result);	// set all params $release, $includecustom, $includeconstant, $buildzip ...
 	}
 	if (preg_match('/includeconstant=/', $argv[$i])) {
 		$tmp=explode(':', $includeconstant, 3);			// $includeconstant has been set with previous parse_str()

+ 4 - 1
htdocs/accountancy/admin/fiscalyear.php

@@ -96,7 +96,7 @@ $help_url = "EN:Module_Double_Entry_Accounting";
 
 llxHeader('', $title, $help_url);
 
-$sql = "SELECT f.rowid, f.label, f.date_start, f.date_end, f.statut, f.entity";
+$sql = "SELECT f.rowid, f.label, f.date_start, f.date_end, f.statut as status, f.entity";
 $sql .= " FROM ".MAIN_DB_PREFIX."accounting_fiscalyear as f";
 $sql .= " WHERE f.entity = ".$conf->entity;
 $sql .= $db->order($sortfield, $sortorder);
@@ -143,7 +143,10 @@ if ($result) {
 		while ($i < $num && $i < $max) {
 			$obj = $db->fetch_object($result);
 
+			$fiscalyearstatic->ref = $obj->rowid;
 			$fiscalyearstatic->id = $obj->rowid;
+			$fiscalyearstatic->statut = $obj->status;
+			$fiscalyearstatic->status = $obj->status;
 
 			print '<tr class="oddeven">';
 			print '<td>';

+ 1 - 1
htdocs/accountancy/journal/sellsjournal.php

@@ -157,7 +157,7 @@ if ($in_bookkeeping == 'notyet') {
 	$sql .= " AND f.rowid NOT IN (SELECT fk_doc FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as ab WHERE ab.doc_type='customer_invoice')";
 	// $sql .= " AND fd.rowid NOT IN (SELECT fk_docdet FROM " . MAIN_DB_PREFIX . "accounting_bookkeeping as ab WHERE ab.doc_type='customer_invoice')";		// Useless, we save one line for all products with same account
 }
-$sql .= " ORDER BY f.datef";
+$sql .= " ORDER BY f.datef, f.ref";
 //print $sql; exit;
 
 dol_syslog('accountancy/journal/sellsjournal.php', LOG_DEBUG);

+ 7 - 1
htdocs/bookmarks/bookmarks.lib.php

@@ -65,7 +65,13 @@ function printDropdownBookmarksList()
 			if ((preg_match('/^search_/', $key) || in_array($key, $authorized_var))
 				&& $val != ''
 				&& !array_key_exists($key, $url_param)) {
-				$url_param[$key] = http_build_query(array(dol_escape_htmltag($key) => dol_escape_htmltag($val)));
+				if (is_array($val)) {
+					foreach ($val as $tmpsubval) {
+						$url_param[] = http_build_query(array(dol_escape_htmltag($key).'[]' => dol_escape_htmltag($tmpsubval)));
+					}
+				} elseif ($val != '') {
+					$url_param[$key] = http_build_query(array(dol_escape_htmltag($key) => dol_escape_htmltag($val)));
+				}
 			}
 		}
 	}

+ 1 - 1
htdocs/categories/class/categorie.class.php

@@ -865,10 +865,10 @@ class Categorie extends CommonObject
 		if (($type == 'customer' || $type == 'supplier') && $user->socid > 0) {
 			$sql .= " AND o.rowid = ".((int) $user->socid);
 		}
+		$sql .= $this->db->order($sortfield, $sortorder);
 		if ($limit > 0 || $offset > 0) {
 			$sql .= $this->db->plimit($limit + 1, $offset);
 		}
-		$sql .= $this->db->order($sortfield, $sortorder);
 
 		dol_syslog(get_class($this)."::getObjectsInCateg", LOG_DEBUG);
 		$resql = $this->db->query($sql);

+ 2 - 2
htdocs/comm/action/card.php

@@ -90,8 +90,8 @@ if ($fulldayevent) {
 // Security check
 $socid = GETPOST('socid', 'int');
 $id = GETPOST('id', 'int');
-if ($user->socid) {
-	$socid = $user->socid;
+if ($user->socid && ($socid != $user->socid)) {
+	accessforbidden();
 }
 
 $error = GETPOST("error");

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

@@ -1566,7 +1566,7 @@ class ActionComm extends CommonObject
 		}
 
 		$canread = 0;
-		if ($user->rights->agenda->myactions->read && $this->authorid == $user->id) {
+		if ($user->rights->agenda->myactions->read && ($this->authorid == $user->id || $this->userownerid == $user->id)) {
 			$canread = 1; // Can read my event
 		}
 		if ($user->rights->agenda->myactions->read && array_key_exists($user->id, $this->userassigned)) {

+ 1 - 0
htdocs/comm/action/list.php

@@ -917,6 +917,7 @@ while ($i < $imaxinloop) {
 	$actionstatic->datep = $db->jdate($obj->dp);
 	$actionstatic->percentage = $obj->percent;
 	$actionstatic->authorid = $obj->fk_user_author;
+	$actionstatic->userownerid = $obj->fk_user_action;
 
 	// Initialize $this->userassigned && this->socpeopleassigned array && this->userownerid
 	// but only if we need it

+ 1 - 1
htdocs/comm/propal/card.php

@@ -1475,7 +1475,7 @@ if (empty($reshook)) {
 			$error++;
 		}
 		if (!$error) {
-			$result = $object->updateExtraField(GETPOST('attribute', 'restricthtml'), 'PROPAL_MODIFY', $user);
+			$result = $object->insertExtraFields('PROPAL_MODIFY');
 			if ($result < 0) {
 				setEventMessages($object->error, $object->errors, 'errors');
 				$error++;

+ 3 - 0
htdocs/commande/class/commande.class.php

@@ -1353,6 +1353,9 @@ class Commande extends CommonOrder
 			$line->marge_tx			= $marginInfos[1];
 			$line->marque_tx		= $marginInfos[2];
 
+			$line->origin           = $object->element;
+			$line->origin_id        = $object->lines[$i]->id;
+
 			// get extrafields from original line
 			$object->lines[$i]->fetch_optionals();
 			foreach ($object->lines[$i]->array_options as $options_key => $value) {

+ 2 - 0
htdocs/commande/list.php

@@ -217,6 +217,7 @@ include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_array_fields.tpl.php';
 $object->fields = dol_sort_array($object->fields, 'position');
 $arrayfields = dol_sort_array($arrayfields, 'position');
 
+$error = 0;
 
 
 /*
@@ -508,6 +509,7 @@ if (empty($reshook)) {
 							} else {
 								$lineid = 0;
 								$error++;
+								$errors[] = $objecttmp->error;
 								break;
 							}
 							// Defined the new fk_parent_line

+ 12 - 2
htdocs/compta/prelevement/line.php

@@ -118,7 +118,13 @@ if ($action == 'confirm_rejet') {
  * View
  */
 
-$invoicestatic = new Facture($db);
+if ($type == 'bank-transfer') {
+	require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
+	$invoicestatic = new FactureFournisseur($db);
+} else {
+	require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
+	$invoicestatic = new Facture($db);
+}
 
 $title = $langs->trans("WithdrawalsLine");
 if ($type == 'bank-transfer') {
@@ -317,7 +323,11 @@ if ($id) {
 				print '<a href="'.DOL_URL_ROOT.'/compta/facture/card.php?facid='.$obj->facid.'">'.$obj->ref."</a></td>\n";
 			}
 
-			print '<td><a href="'.DOL_URL_ROOT.'/comm/card.php?socid='.$obj->socid.'">';
+			if ($type == 'bank-transfer') {
+				print '<td><a href="'.DOL_URL_ROOT.'/fourn/card.php?socid='.$obj->socid.'">';
+			} else {
+				print '<td><a href="'.DOL_URL_ROOT.'/comm/card.php?socid='.$obj->socid.'">';
+			}
 			print img_object($langs->trans("ShowCompany"), "company").' '.$obj->name."</a></td>\n";
 
 			print '<td class="right"><span class="amount">'.price($obj->total_ttc)."</span></td>\n";

+ 1 - 1
htdocs/contact/consumption.php

@@ -376,7 +376,7 @@ if ($sql_select) {
 
 	$num = $db->num_rows($resql);
 
-	$param = "&socid=".urlencode($socid)."&type_element=".urlencode($type_element);
+	$param = "&socid=".urlencode($socid)."&type_element=".urlencode($type_element)."&id=".urlencode($id);
 	if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) {
 		$param .= '&contextpage='.urlencode($contextpage);
 	}

+ 1 - 1
htdocs/core/actions_massactions.inc.php

@@ -752,7 +752,7 @@ if (!$error && $massaction == "builddoc" && $permissiontoread && !GETPOST('butto
 		$arrayofinclusion[] = '^'.preg_quote(dol_sanitizeFileName($tmppdf), '/').'\.pdf$';
 	}
 	foreach ($listofobjectref as $tmppdf) {
-		$arrayofinclusion[] = '^'.preg_quote(dol_sanitizeFileName($tmppdf), '/').'_[a-zA-Z0-9\-\_\']+\.pdf$'; // To include PDF generated from ODX files
+		$arrayofinclusion[] = '^'.preg_quote(dol_sanitizeFileName($tmppdf), '/').'_[a-zA-Z0-9\-\_\'\&\.]+\.pdf$'; // To include PDF generated from ODX files
 	}
 	$listoffiles = dol_dir_list($uploaddir, 'all', 1, implode('|', $arrayofinclusion), '\.meta$|\.png', 'date', SORT_DESC, 0, true);
 

+ 3 - 3
htdocs/core/boxes/box_graph_product_distribution.php

@@ -161,7 +161,7 @@ class box_graph_product_distribution extends ModeleBoxes
 				$showpointvalue = 1;
 				$nocolor = 0;
 				$stats_proposal = new PropaleStats($this->db, $socid, ($userid > 0 ? $userid : 0));
-				$data2 = $stats_proposal->getAllByProductEntry($year, (GETPOST('action', 'aZ09') == $refreshaction ?-1 : (3600 * 24)), 5);
+				$data2 = $stats_proposal->getAllByProductEntry($year, (GETPOST('action', 'aZ09') == $refreshaction ?-1 : (3600 * 24)), $max);
 				if (empty($data2)) {
 					$showpointvalue = 0;
 					$nocolor = 1;
@@ -224,7 +224,7 @@ class box_graph_product_distribution extends ModeleBoxes
 				$nocolor = 0;
 				$mode = 'customer';
 				$stats_order = new CommandeStats($this->db, $socid, $mode, ($userid > 0 ? $userid : 0));
-				$data3 = $stats_order->getAllByProductEntry($year, (GETPOST('action', 'aZ09') == $refreshaction ?-1 : (3600 * 24)), 5);
+				$data3 = $stats_order->getAllByProductEntry($year, (GETPOST('action', 'aZ09') == $refreshaction ?-1 : (3600 * 24)), $max);
 				if (empty($data3)) {
 					$showpointvalue = 0;
 					$nocolor = 1;
@@ -288,7 +288,7 @@ class box_graph_product_distribution extends ModeleBoxes
 				$nocolor = 0;
 				$mode = 'customer';
 				$stats_invoice = new FactureStats($this->db, $socid, $mode, ($userid > 0 ? $userid : 0));
-				$data1 = $stats_invoice->getAllByProductEntry($year, (GETPOST('action', 'aZ09') == $refreshaction ?-1 : (3600 * 24)), 5);
+				$data1 = $stats_invoice->getAllByProductEntry($year, (GETPOST('action', 'aZ09') == $refreshaction ?-1 : (3600 * 24)), $max);
 
 				if (empty($data1)) {
 					$showpointvalue = 0;

+ 9 - 3
htdocs/core/class/commonobject.class.php

@@ -5687,9 +5687,15 @@ abstract class CommonObject
 	 */
 	public function addThumbs($file)
 	{
-		global $maxwidthsmall, $maxheightsmall, $maxwidthmini, $maxheightmini, $quality;
-
-		require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php'; // This define also $maxwidthsmall, $quality, ...
+		require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
+
+		$tmparraysize = getDefaultImageSizes();
+		$maxwidthsmall = $tmparraysize['maxwidthsmall'];
+		$maxheightsmall = $tmparraysize['maxheightsmall'];
+		$maxwidthmini = $tmparraysize['maxwidthmini'];
+		$maxheightmini = $tmparraysize['maxheightmini'];
+		//$quality = $tmparraysize['quality'];
+		$quality = 50;	// For thumbs, we force quality to 50
 
 		$file_osencoded = dol_osencode($file);
 		if (file_exists($file_osencoded)) {

+ 3 - 0
htdocs/core/class/extrafields.class.php

@@ -1084,6 +1084,9 @@ class ExtraFields
 						continue;
 					}
 
+					$valarray = explode('|', $val);
+					$val = $valarray[0];
+
 					if ($langfile && $val) {
 						$options[$okey] = $langs->trans($val);
 					} else {

+ 5 - 1
htdocs/core/class/html.form.class.php

@@ -6788,7 +6788,11 @@ class Form
 			if (empty($labeladddateof)) {
 				$labeladddateof = $langs->trans("DateInvoice");
 			}
-			$retstring .= ' - <button class="dpInvisibleButtons datenowlink" id="dateofinvoice" type="button" name="_dateofinvoice" value="now" onclick="console.log(\'Click on now link\'); jQuery(\'#re\').val(\''.dol_print_date($adddateof, 'dayinputnoreduce').'\');jQuery(\'#reday\').val(\''.$tmparray['mday'].'\');jQuery(\'#remonth\').val(\''.$tmparray['mon'].'\');jQuery(\'#reyear\').val(\''.$tmparray['year'].'\');">'.$labeladddateof.'</a>';
+			$reset_scripts = 'jQuery(\'#'.$prefix.'\').val(\''.dol_print_date($adddateof, 'dayinputnoreduce').'\');';
+			$reset_scripts .= 'jQuery(\'#'.$prefix.'day\').val(\''.$tmparray['mday'].'\');';
+			$reset_scripts .= 'jQuery(\'#'.$prefix.'month\').val(\''.$tmparray['mon'].'\');';
+			$reset_scripts .= 'jQuery(\'#'.$prefix.'year\').val(\''.$tmparray['year'].'\');';
+			$retstring .= ' - <button class="dpInvisibleButtons datenowlink" id="dateofinvoice" type="button" name="_dateofinvoice" value="now" onclick="'.$reset_scripts.'">'.$labeladddateof.'</a>';
 		}
 
 		return $retstring;

+ 14 - 8
htdocs/core/lib/files.lib.php

@@ -1690,10 +1690,16 @@ function dol_add_file_process($upload_dir, $allowoverwrite = 0, $donotupdatesess
 				$resupload = dol_move_uploaded_file($TFile['tmp_name'][$i], $destfull, $allowoverwrite, 0, $TFile['error'][$i], 0, $varfiles, $upload_dir);
 
 				if (is_numeric($resupload) && $resupload > 0) {   // $resupload can be 'ErrorFileAlreadyExists'
-					global $maxwidthsmall, $maxheightsmall, $maxwidthmini, $maxheightmini;
-
 					include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
 
+					$tmparraysize = getDefaultImageSizes();
+					$maxwidthsmall = $tmparraysize['maxwidthsmall'];
+					$maxheightsmall = $tmparraysize['maxheightsmall'];
+					$maxwidthmini = $tmparraysize['maxwidthmini'];
+					$maxheightmini = $tmparraysize['maxheightmini'];
+					//$quality = $tmparraysize['quality'];
+					$quality = 50;	// For thumbs, we force quality to 50
+
 					// Generate thumbs.
 					if ($generatethumbs) {
 						if (image_format_supported($destfull) == 1) {
@@ -1701,10 +1707,10 @@ function dol_add_file_process($upload_dir, $allowoverwrite = 0, $donotupdatesess
 							// We can't use $object->addThumbs here because there is no $object known
 
 							// Used on logon for example
-							$imgThumbSmall = vignette($destfull, $maxwidthsmall, $maxheightsmall, '_small', 50, "thumbs");
+							$imgThumbSmall = vignette($destfull, $maxwidthsmall, $maxheightsmall, '_small', $quality, "thumbs");
 							// Create mini thumbs for image (Ratio is near 16/9)
 							// Used on menu or for setup page for example
-							$imgThumbMini = vignette($destfull, $maxwidthmini, $maxheightmini, '_mini', 50, "thumbs");
+							$imgThumbMini = vignette($destfull, $maxwidthmini, $maxheightmini, '_mini', $quality, "thumbs");
 						}
 					}
 
@@ -2813,7 +2819,7 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity,
 		}
 		$original_file = $conf->commande->multidir_output[$entity].'/'.$original_file;
 		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."commande WHERE ref='".$db->escape($refname)."' AND entity IN (".getEntity('order').")";
-	} elseif ($modulepart == 'project' && !empty($conf->project->dir_output)) {
+	} elseif ($modulepart == 'project' && !empty($conf->project->multidir_output[$entity])) {
 		// Wrapping pour les projets
 		if ($fuser->rights->projet->{$lire} || preg_match('/^specimen/i', $original_file)) {
 			$accessallowed = 1;
@@ -2825,9 +2831,9 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity,
 				$accessallowed = checkUserAccessToObject($user, array('projet'), $tmpproject->id, 'projet&project', '', '', 'rowid', '');
 			}
 		}
-		$original_file = $conf->project->dir_output.'/'.$original_file;
+		$original_file = $conf->project->multidir_output[$entity].'/'.$original_file;
 		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."projet WHERE ref='".$db->escape($refname)."' AND entity IN (".getEntity('project').")";
-	} elseif ($modulepart == 'project_task' && !empty($conf->project->dir_output)) {
+	} elseif ($modulepart == 'project_task' && !empty($conf->project->multidir_output[$entity])) {
 		if ($fuser->rights->projet->{$lire} || preg_match('/^specimen/i', $original_file)) {
 			$accessallowed = 1;
 			// If we known $id of project, call checkUserAccessToObject to check permission on properties and contact of project
@@ -2838,7 +2844,7 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity,
 				$accessallowed = checkUserAccessToObject($user, array('projet_task'), $tmptask->id, 'projet_task&project', '', '', 'rowid', '');
 			}
 		}
-		$original_file = $conf->project->dir_output.'/'.$original_file;
+		$original_file = $conf->project->multidir_output[$entity].'/'.$original_file;
 		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."projet WHERE ref='".$db->escape($refname)."' AND entity IN (".getEntity('project').")";
 	} elseif (($modulepart == 'commande_fournisseur' || $modulepart == 'order_supplier') && !empty($conf->fournisseur->commande->dir_output)) {
 		// Wrapping pour les commandes fournisseurs

+ 22 - 0
htdocs/core/lib/images.lib.php

@@ -23,12 +23,34 @@
  */
 
 // Define size of logo small and mini
+// TODO Remove this and call getDefaultImageSizes() instead
 $maxwidthsmall = 480;
 $maxheightsmall = 270; // Near 16/9eme
 $maxwidthmini = 128;
 $maxheightmini = 72; // 16/9eme
 $quality = 80;
 
+/**
+ *      Return default values for image sizes
+ *
+ *      @return array		Array of default values
+ */
+function getDefaultImageSizes()
+{
+	$maxwidthsmall = 480;
+	$maxheightsmall = 270; // Near 16/9eme
+	$maxwidthmini = 128;
+	$maxheightmini = 72; // 16/9eme
+	$quality = 80;
+
+	return array(
+		'maxwidthsmall' => $maxwidthsmall,
+		'maxheightsmall' => $maxheightsmall,
+		'maxwidthmini' => $maxwidthmini,
+		'maxheightmini' => $maxheightmini,
+		'quality' => $quality
+	);
+}
 
 /**
  *      Return if a filename is file name of a supported image format

+ 2 - 2
htdocs/core/lib/project.lib.php

@@ -275,7 +275,7 @@ function project_prepare_head(Project $project, $moreparam = '')
 	} else {
 		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
 		require_once DOL_DOCUMENT_ROOT.'/core/class/link.class.php';
-		$upload_dir = $conf->project->dir_output."/".dol_sanitizeFileName($project->ref);
+		$upload_dir = $conf->project->multidir_output[$project->entity]."/".dol_sanitizeFileName($project->ref);
 		$nbFiles = count(dol_dir_list($upload_dir, 'files', 0, '', '(\.meta|_preview.*\.png)$'));
 		$nbLinks = Link::count($db, $project->element, $project->id);
 		$totalAttached = $nbFiles + $nbLinks;
@@ -401,7 +401,7 @@ function task_prepare_head($object)
 	}
 
 	$head[$h][0] = DOL_URL_ROOT.'/projet/tasks/document.php?id='.$object->id.(GETPOST('withproject') ? '&withproject=1' : '');
-	$filesdir = $conf->project->dir_output."/".dol_sanitizeFileName($object->project->ref).'/'.dol_sanitizeFileName($object->ref);
+	$filesdir = $conf->project->multidir_output[$object->entity]."/".dol_sanitizeFileName($object->project->ref).'/'.dol_sanitizeFileName($object->ref);
 	include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
 	include_once DOL_DOCUMENT_ROOT.'/core/class/link.class.php';
 	$nbFiles = count(dol_dir_list($filesdir, 'files', 0, '', '(\.meta|_preview.*\.png)$'));

+ 8 - 4
htdocs/core/lib/website.lib.php

@@ -35,8 +35,10 @@ function dolStripPhpCode($str, $replacewith = '')
 
 	$newstr = '';
 
-	//split on each opening tag
-	$parts = explode('<?php', $str);
+	// Split on each opening tag
+	//$parts = explode('<?php', $str);
+	$parts = preg_split('/'.preg_quote('<?php', '/').'/i', $str);
+
 	if (!empty($parts)) {
 		$i = 0;
 		foreach ($parts as $part) {
@@ -77,8 +79,10 @@ function dolKeepOnlyPhpCode($str)
 
 	$newstr = '';
 
-	//split on each opening tag
-	$parts = explode('<?php', $str);
+	// Split on each opening tag
+	//$parts = explode('<?php', $str);
+	$parts = preg_split('/'.preg_quote('<?php', '/').'/i', $str);
+
 	if (!empty($parts)) {
 		$i = 0;
 		foreach ($parts as $part) {

+ 3 - 2
htdocs/core/menus/init_menu_auguria.sql

@@ -9,6 +9,7 @@ delete from llx_menu where menu_handler=__HANDLER__ and entity=__ENTITY__;
 -- table llx_menu
 --
 
+
 -- Top-Menu
 -- old:              (module, enabled, rowid, ...)
 insert into llx_menu (rowid, module, enabled, menu_handler, type, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values (  1__+MAX_llx_menu__, '', '1',  __HANDLER__, 'top', 'home', '', 0, '/index.php?mainmenu=home&amp;leftmenu=', 'Home', -1, '', '', '', 2, 10, __ENTITY__);
@@ -17,7 +18,7 @@ insert into llx_menu (rowid, module, enabled, menu_handler, type, mainmenu, left
 insert into llx_menu (rowid, module, enabled, menu_handler, type, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ( 16__+MAX_llx_menu__, 'bom|mrp', '$conf->bom->enabled || $conf->mrp->enabled', __HANDLER__, 'top', 'mrp',    '', 0, '/mrp/index.php?mainmenu=mrp&amp;leftmenu=', 'MRP', -1, 'mrp', '$user->rights->bom->read||$user->rights->mrp->read', '', 0, 31, __ENTITY__);
 insert into llx_menu (rowid, module, enabled, menu_handler, type, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values (  7__+MAX_llx_menu__, 'projet', '$conf->project->enabled', __HANDLER__, 'top', 'project',     '', 0, '/projet/index.php?mainmenu=project&amp;leftmenu=', 'Projects', -1, 'projects', '$user->rights->projet->lire', '', 2, 32, __ENTITY__);
 insert into llx_menu (rowid, module, enabled, menu_handler, type, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values (  5__+MAX_llx_menu__, 'propal|commande|fournisseur|supplier_order|supplier_invoice|contrat|ficheinter', '$conf->propal->enabled || $conf->commande->enabled || $conf->supplier_order->enabled || $conf->contrat->enabled || $conf->ficheinter->enabled', __HANDLER__, 'top', 'commercial',  '', 0, '/comm/index.php?mainmenu=commercial&amp;leftmenu=', 'Commercial', -1, 'commercial', '$user->rights->societe->lire || $user->rights->societe->contact->lire', '', 2, 40, __ENTITY__);
-insert into llx_menu (rowid, module, enabled, menu_handler, type, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values (  6__+MAX_llx_menu__, 'facture|don|tax|salaries|loan|banque', '$conf->comptabilite->enabled || $conf->accounting->enabled || $conf->facture->enabled || $conf->don->enabled  || $conf->tax->enabled || $conf->salaries->enabled || $conf->supplier_invoice->enabled || $conf->loan->enabled || $conf->banque->enabled', __HANDLER__, 'top', 'billing', '', 0, '/compta/index.php?mainmenu=billing&amp;leftmenu=', 'MenuFinancial', -1, 'compta', '$user->rights->facture->lire|| $user->rights->don->lire || $user->rights->tax->charges->lire || $user->rights->salaries->read || $user->rights->loan->read || $user->rights->banque->lire', '', 2, 50, __ENTITY__);
+insert into llx_menu (rowid, module, enabled, menu_handler, type, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values (  6__+MAX_llx_menu__, 'facture|don|tax|salaries|loan|banque', '$conf->comptabilite->enabled || $conf->accounting->enabled || $conf->facture->enabled || $conf->don->enabled  || $conf->tax->enabled || $conf->salaries->enabled || $conf->supplier_invoice->enabled || $conf->loan->enabled || $conf->banque->enabled', __HANDLER__, 'top', 'billing', '', 0, '/compta/index.php?mainmenu=billing&amp;leftmenu=', 'MenuFinancial', -1, 'compta', '$user->rights->facture->lire|| $user->rights->don->lire || $user->rights->tax->charges->lire || $user->rights->salaries->read || $user->rights->loan->read || $user->rights->banque->lire || ($user->rights->fournisseur->facture->lire || $user->rights->supplier_invoice->lire', '', 2, 50, __ENTITY__);
 insert into llx_menu (rowid, module, enabled, menu_handler, type, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ( 14__+MAX_llx_menu__, 'banque|prelevement', '$conf->banque->enabled || $conf->prelevement->enabled', __HANDLER__, 'top', 'bank', '', 0, '/compta/bank/list.php?mainmenu=bank&amp;leftmenu=bank', 'MenuBankCash', -1, 'banks', '$user->rights->banque->lire || $user->rights->prelevement->bons->lire', '', 0, 52, __ENTITY__);
 insert into llx_menu (rowid, module, enabled, menu_handler, type, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values (  9__+MAX_llx_menu__, 'comptabilite|accounting|asset', '$conf->comptabilite->enabled || $conf->accounting->enabled || $conf->asset->enabled', __HANDLER__, 'top', 'accountancy', '', 0, '/compta/index.php?mainmenu=accountancy&amp;leftmenu=accountancy', 'MenuAccountancy', -1, 'main', '$user->rights->compta->resultat->lire || $user->rights->accounting->mouvements->lire || $user->rights->asset->read', '', 2, 54, __ENTITY__);
 insert into llx_menu (rowid, module, enabled, menu_handler, type, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values (  8__+MAX_llx_menu__, '', '', __HANDLER__, 'top', 'tools',       '', 0, '/core/tools.php?mainmenu=tools&amp;leftmenu=', 'Tools', -1, 'other', '', '', 2, 90, __ENTITY__);
@@ -25,7 +26,7 @@ insert into llx_menu (rowid, module, enabled, menu_handler, type, mainmenu, left
 insert into llx_menu (rowid, module, enabled, menu_handler, type, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ( 15__+MAX_llx_menu__, 'hrm|holiday|deplacement|expensereport', '$conf->hrm->enabled || $conf->holiday->enabled || $conf->deplacement->enabled || $conf->expensereport->enabled', __HANDLER__, 'top', 'hrm', '', 0, '/hrm/index.php?mainmenu=hrm&amp;leftmenu=', 'HRM', -1, 'holiday', '$user->rights->user->user->lire || $user->rights->holiday->read || $user->rights->deplacement->lire || $user->rights->expensereport->lire', '', 0, 80, __ENTITY__);
 
 
--- Sub-Menues
+-- Sub-Menus
 
 -- Home - Dashboard
 insert into llx_menu (module, enabled, menu_handler, type, rowid, mainmenu, leftmenu, fk_menu, url, titre, level, langs, perms, target, usertype, position, entity) values ('', '1', __HANDLER__, 'left', 90__+MAX_llx_menu__, 'home', '', 1__+MAX_llx_menu__, '/index.php', 'MyDashboard', 0, '', '', '', 2, 0, __ENTITY__);

+ 36 - 7
htdocs/core/modules/expedition/doc/pdf_espadon.modules.php

@@ -114,6 +114,16 @@ class pdf_espadon extends ModelePdfExpedition
 	 */
 	public $emetteur;
 
+	// MMI Missing fields
+	public $posxdesc;
+	public $watermark;
+	public $tabTitleHeight;
+	public $atleastonephoto;
+	public $posxpicture;
+	public $posxweightvol;
+	public $defaultContentsFieldsStyle;
+	public $defaultTitlesFieldsStyle;
+	public $cols;
 
 	/**
 	 *	Constructor
@@ -374,6 +384,8 @@ class pdf_espadon extends ModelePdfExpedition
 
 				if (!empty($notetoshow) || !empty($object->shipping_method_id) || !empty($object->tracking_number)) {
 					$tab_top -= 2;
+					$tab_topbeforetrackingnumber = $tab_top;
+					$height_trackingnumber = 4;
 
 					if (!empty($object->shipping_method_id) || !empty($object->tracking_number)) {
 						$label = '';
@@ -390,15 +402,17 @@ class pdf_espadon extends ModelePdfExpedition
 							$label .= ($label ?' / ' :'').$outputlangs->transnoentities("TrackingNumber")." : ".$object->tracking_number;
 
 						$pdf->SetFont('', 'B', $default_font_size - 2);
-						$pdf->writeHTMLCell(160, 4, $this->posxdesc - 1, $tab_top, $label, 0, 1, false, true, 'L');
+						$pdf->writeHTMLCell(160, $height_trackingnumber, $this->posxdesc - 1, $tab_top, $label, 0, 1, false, true, 'L');
 						$tab_top += 5;
 					}
 
 					// Notes
 					$pagenb = $pdf->getPage();
+
 					if (!empty($notetoshow)) {
 						$tab_top += 2;
 
+
 						$tab_width = $this->page_largeur - $this->marge_gauche - $this->marge_droite;
 						$pageposbeforenote = $pagenb;
 
@@ -460,11 +474,21 @@ class pdf_espadon extends ModelePdfExpedition
 								$pdf->SetDrawColor(128, 128, 128);
 								// Draw note frame
 								if ($i > $pageposbeforenote) {
-									$height_note = $this->page_hauteur - ($tab_top_newpage + $heightforfooter);
-									$pdf->Rect($this->marge_gauche, $tab_top_newpage - 1, $tab_width, $height_note + 1);
+									if (empty($height_trackingnumber)) {
+										$height_note = $this->page_hauteur - ($tab_top_newpage + $heightforfooter);
+									} else {
+										$height_note = $this->page_hauteur - ($tab_top_newpage + $heightforfooter) + $height_trackingnumber + 1;
+										$tab_top_newpage = $tab_topbeforetrackingnumber;
+									}
+									$pdf->Rect($this->marge_gauche, $tab_top_newpage - 1, $tab_width, $height_note + 2);
 								} else {
-									$height_note = $this->page_hauteur - ($tab_top + $heightforfooter);
-									$pdf->Rect($this->marge_gauche, $tab_top - 1, $tab_width, $height_note + 1);
+									if (empty($height_trackingnumber)) {
+										$height_note = $this->page_hauteur - ($tab_top + $heightforfooter);
+									} else {
+										$height_note = $this->page_hauteur - ($tab_top + $heightforfooter)+ $height_trackingnumber + 1;
+										$tab_top = $tab_topbeforetrackingnumber;
+									}
+									$pdf->Rect($this->marge_gauche, $tab_top - 1, $tab_width, $height_note + 2);
 								}
 
 								// Add footer
@@ -484,8 +508,13 @@ class pdf_espadon extends ModelePdfExpedition
 						{
 							$pdf->commitTransaction();
 							$posyafter = $pdf->GetY();
-							$height_note = $posyafter - $tab_top;
-							$pdf->Rect($this->marge_gauche, $tab_top - 1, $tab_width, $height_note + 1);
+							if (empty($height_trackingnumber)) {
+								$height_note = $posyafter - $tab_top + 1;
+							} else {
+								$height_note = $posyafter - $tab_top + $height_trackingnumber + 1;
+								$tab_top = $tab_topbeforetrackingnumber;
+							}
+							$pdf->Rect($this->marge_gauche, $tab_top - 1, $tab_width, $height_note + 2);
 
 
 							if ($posyafter > ($this->page_hauteur - ($heightforfooter + $heightforfreetext + 20))) {

+ 2 - 2
htdocs/core/modules/project/doc/pdf_baleine.modules.php

@@ -195,11 +195,11 @@ class pdf_baleine extends ModelePDFProjects
 		// Load traductions files required by page
 		$outputlangs->loadLangs(array("main", "dict", "companies", "projects"));
 
-		if ($conf->project->dir_output) {
+		if ($conf->project->multidir_output[$object->entity]) {
 			//$nblines = count($object->lines);  // This is set later with array of tasks
 
 			$objectref = dol_sanitizeFileName($object->ref);
-			$dir = $conf->project->dir_output;
+			$dir = $conf->project->multidir_output[$object->entity];
 			if (!preg_match('/specimen/i', $objectref)) {
 				$dir .= "/".$objectref;
 			}

+ 2 - 2
htdocs/core/modules/project/doc/pdf_beluga.modules.php

@@ -242,11 +242,11 @@ class pdf_beluga extends ModelePDFProjects
 		// Load traductions files required by page
 		$outputlangs->loadLangs(array("main", "dict", "companies", "projects"));
 
-		if ($conf->project->dir_output) {
+		if ($conf->project->multidir_output[$object->entity]) {
 			//$nblines = count($object->lines);  // This is set later with array of tasks
 
 			$objectref = dol_sanitizeFileName($object->ref);
-			$dir = $conf->project->dir_output;
+			$dir = $conf->project->multidir_output[$object->entity];
 			if (!preg_match('/specimen/i', $objectref)) {
 				$dir .= "/".$objectref;
 			}

+ 2 - 2
htdocs/core/modules/project/doc/pdf_timespent.modules.php

@@ -195,11 +195,11 @@ class pdf_timespent extends ModelePDFProjects
 		// Load traductions files required by page
 		$outputlangs->loadLangs(array("main", "dict", "companies", "projects"));
 
-		if ($conf->project->dir_output) {
+		if ($conf->project->multidir_output[$object->entity]) {
 			//$nblines = count($object->lines);  // This is set later with array of tasks
 
 			$objectref = dol_sanitizeFileName($object->ref);
-			$dir = $conf->project->dir_output;
+			$dir = $conf->project->multidir_output[$object->entity];
 			if (!preg_match('/specimen/i', $objectref)) {
 				$dir .= "/".$objectref;
 			}

+ 22 - 3
htdocs/expedition/card.php

@@ -327,7 +327,8 @@ if (empty($reshook)) {
 
 		//var_dump($batch_line[2]);
 
-		if ($totalqty > 0) {		// There is at least one thing to ship
+		if ($totalqty > 0 || !empty($conf->global->SHIPMENT_GETS_ALL_ORDER_PRODUCTS)) {		// There is at least one thing to ship
+			//var_dump($_POST);exit;
 			for ($i = 0; $i < $num; $i++) {
 				$qty = "qtyl".$i;
 				if (!isset($batch_line[$i])) {
@@ -336,7 +337,7 @@ if (empty($reshook)) {
 						//shipment from multiple stock locations
 						$nbstockline = count($stockLine[$i]);
 						for ($j = 0; $j < $nbstockline; $j++) {
-							if ($stockLine[$i][$j]['qty'] > 0) {
+							if ($stockLine[$i][$j]['qty'] > 0 || ($stockLine[$i][$j]['qty'] == 0 && !empty($conf->global->SHIPMENT_GETS_ALL_ORDER_PRODUCTS))) {
 								$ret = $object->addline($stockLine[$i][$j]['warehouse_id'], $stockLine[$i][$j]['ix_l'], $stockLine[$i][$j]['qty'], $array_options[$i]);
 								if ($ret < 0) {
 									setEventMessages($object->error, $object->errors, 'errors');
@@ -345,7 +346,7 @@ if (empty($reshook)) {
 							}
 						}
 					} else {
-						if (GETPOST($qty, 'int') > 0 || (GETPOST($qty, 'int') == 0 && $conf->global->SHIPMENT_GETS_ALL_ORDER_PRODUCTS)) {
+						if (GETPOST($qty, 'int') > 0 || (GETPOST($qty, 'int') == 0 && !empty($conf->global->SHIPMENT_GETS_ALL_ORDER_PRODUCTS))) {
 							$ent = "entl".$i;
 							$idl = "idl".$i;
 							$entrepot_id = is_numeric(GETPOST($ent, 'int')) ?GETPOST($ent, 'int') : GETPOST('entrepot_id', 'int');
@@ -595,6 +596,7 @@ if (empty($reshook)) {
 		$num_prod = count($lines);
 		for ($i = 0; $i < $num_prod; $i++) {
 			if ($lines[$i]->id == $line_id) {		// we have found line to update
+				$update_done = false;
 				$line = new ExpeditionLigne($db);
 				$line->fk_expedition = $object->id;
 
@@ -637,6 +639,8 @@ if (empty($reshook)) {
 								if ($line->update($user) < 0) {
 									setEventMessages($line->error, $line->errors, 'errors');
 									$error++;
+								} else {
+									$update_done=true;
 								}
 							} else {
 								setEventMessages($lotStock->error, $lotStock->errors, 'errors');
@@ -681,6 +685,8 @@ if (empty($reshook)) {
 									if ($line->update($user) < 0) {
 										setEventMessages($line->error, $line->errors, 'errors');
 										$error++;
+									} else {
+										$update_done=true;
 									}
 								} else {
 									setEventMessages($line->error, $line->errors, 'errors');
@@ -700,6 +706,8 @@ if (empty($reshook)) {
 								if ($object->create_line_batch($line, $line->array_options) < 0) {
 									setEventMessages($object->error, $object->errors, 'errors');
 									$error++;
+								} else {
+									$update_done=true;
 								}
 							}
 						} else {
@@ -737,6 +745,8 @@ if (empty($reshook)) {
 										if ($line->update($user) < 0) {
 											setEventMessages($line->error, $line->errors, 'errors');
 											$error++;
+										} else {
+											$update_done=true;
 										}
 									}
 									unset($_POST[$stockLocation]);
@@ -751,6 +761,8 @@ if (empty($reshook)) {
 							if ($line->update($user) < 0) {
 								setEventMessages($line->error, $line->errors, 'errors');
 								$error++;
+							} else {
+								$update_done=true;
 							}
 							unset($_POST[$qty]);
 						}
@@ -763,10 +775,17 @@ if (empty($reshook)) {
 						if ($line->update($user) < 0) {
 							setEventMessages($line->error, $line->errors, 'errors');
 							$error++;
+						} else {
+							$update_done=true;
 						}
 						unset($_POST[$qty]);
 					}
 				}
+
+				if (empty($update_done)) {
+					$line->id = $lines[$i]->id;
+					$line->insertExtraFields();
+				}
 			}
 		}
 

+ 16 - 4
htdocs/expensereport/card.php

@@ -2643,10 +2643,22 @@ if ($action == 'create') {
 							,async:false
 							,dataType:"json"
 							,success:function(response) {
-                                if (response.response_status == "success"){
-                                jQuery("#value_unit_ht").val(response.data);
-                                jQuery("#value_unit_ht").trigger("change");
-                                jQuery("#value_unit").val("");
+								if (response.response_status == "success"){';
+
+				if (!empty($conf->global->EXPENSEREPORT_FORCE_LINE_AMOUNTS_INCLUDING_TAXES_ONLY)) {
+					print '
+									jQuery("#value_unit").val(parseFloat(response.data) * (100 + parseFloat(tva)) / 100);
+									jQuery("#value_unit").trigger("change");
+				                    ';
+				} else {
+					print '
+									jQuery("#value_unit_ht").val(response.data);
+									jQuery("#value_unit_ht").trigger("change");
+									jQuery("#value_unit").val("");
+									';
+				}
+
+				print '
                                 } else if(response.response_status == "error" && response.errorMessage != undefined && response.errorMessage.length > 0 ){
                                     $.jnotify(response.errorMessage, "error", {timeout: 0, type: "error"},{ remove: function (){} } );
                                 }

+ 4 - 4
htdocs/imports/import.php

@@ -1079,9 +1079,9 @@ if ($step == 4 && $datatoimport) {
 		$valforsourcefieldnb[$lefti] = $key;
 		$lefti++;
 
-		if ($lefti > count($fieldstarget)) {
+		/*if ($lefti > count($fieldstarget)) {
 			break; // Other fields are in the not imported area
-		}
+		}*/
 	}
 	//var_dump($valforsourcefieldnb);
 
@@ -1134,9 +1134,9 @@ if ($step == 4 && $datatoimport) {
 
 	print '<table class="nobordernopadding centpercent tableimport">';
 	foreach ($fieldssource as $code => $line) {	// $fieldssource is an array code=column num,  line=content on first line for column in source file.
-		if ($i == $minpos) {
+		/*if ($i == $minpos) {
 			break;
-		}
+		}*/
 		print '<tr style="height:'.$height.'" class="trimport oddevenimport">';
 		$entity = (!empty($objimport->array_import_entities[0][$code]) ? $objimport->array_import_entities[0][$code] : $objimport->array_import_icon[0]);
 

+ 0 - 0
htdocs/install/doctemplates/index.html


+ 0 - 0
htdocs/install/doctemplates/websites/index.html


+ 2 - 0
htdocs/install/mysql/migration/15.0.0-16.0.0.sql

@@ -738,6 +738,8 @@ ALTER TABLE llx_expedition ADD COLUMN billed smallint    DEFAULT 0;
 
 ALTER TABLE llx_loan_schedule ADD UNIQUE INDEX uk_loan_schedule_ref (fk_loan, datep);
 
+ALTER TABLE llx_overwrite_trans DROP INDEX uk_overwrite_trans;
+ALTER TABLE llx_overwrite_trans ADD UNIQUE INDEX uk_overwrite_trans(lang, transkey, entity);
 
 
 -- Bank Thirdparty

+ 13 - 8
htdocs/install/mysql/migration/repair.sql

@@ -569,14 +569,19 @@ UPDATE llx_facturedet SET situation_percent = 100 WHERE situation_percent IS NUL
 DELETE FROM llx_rights_def WHERE module = 'hrm' AND perms = 'employee';
 
 
--- Sequence to fix the content of llx_bank.amount_main_currency
--- Note: amount is amount in currency of bank account
+
+-- Sequence to fix the content of llx_bank.amount_main_currency (value was empty and should not for payment on bank account with a different currency so when amount_main_currency is different than amount)
+-- Note: amount is amount in the currency of the bank account
 -- Note: pamount is always amount into the main currency
--- Note: pmulticurrencyamount is in currency of invoice 
--- Note: amount_main_currency must be amount in main currency
+-- Note: pmulticurrencyamount is in the currency of invoice 
+-- Note: amount_main_currency must be NULL or amount in main currency of company (we set it when the currency of the bank account differs from main currency)
 -- DROP TABLE tmp_bank;
--- CREATE TABLE tmp_bank SELECT b.rowid, b.amount, p.rowid as pid, p.amount as pamount, p.multicurrency_amount as pmulticurrencyamount FROM llx_bank as b INNER JOIN llx_bank_url as bu ON bu.fk_bank=b.rowid AND bu.type = 'payment' INNER JOIN llx_paiement as p ON bu.url_id = p.rowid WHERE p.multicurrency_amount <> 0 AND p.multicurrency_amount <> p.amount;
--- UPDATE llx_bank as b SET b.amount_main_currency = (SELECT tb.pamount FROM tmp_bank as tb WHERE tb.rowid = b.rowid) WHERE b.amount_main_currency IS NULL;
+-- CREATE TABLE tmp_bank SELECT b.rowid, b.amount, p.rowid as pid, p.amount as pamount, p.multicurrency_amount as pmulticurrencyamount, b.datec FROM llx_bank as b INNER JOIN llx_bank_url as bu ON bu.fk_bank=b.rowid AND bu.type = 'payment' INNER JOIN llx_paiement as p ON bu.url_id = p.rowid WHERE p.multicurrency_amount <> 0 AND p.multicurrency_amount <> p.amount;
+-- UPDATE llx_bank as b SET b.amount_main_currency = (SELECT tb.pamount FROM tmp_bank as tb WHERE tb.rowid = b.rowid) WHERE b.amount_main_currency IS NULL AND b.rowid IN (SELECT rowid FROM tmp_bank);
 -- DROP TABLE tmp_bank2;
--- CREATE TABLE tmp_bank2 SELECT b.rowid, b.amount, p.rowid as pid, p.amount as pamount, p.multicurrency_amount as pmulticurrencyamount FROM llx_bank as b INNER JOIN llx_bank_url as bu ON bu.fk_bank=b.rowid AND bu.type = 'payment_supplier' INNER JOIN llx_paiementfourn as p ON bu.url_id = p.rowid WHERE p.multicurrency_amount <> 0 AND p.multicurrency_amount <> p.amount;
--- UPDATE llx_bank as b SET b.amount_main_currency = (SELECT tb.pamount FROM tmp_bank2 as tb WHERE tb.rowid = b.rowid) WHERE b.amount_main_currency IS NULL;
+-- CREATE TABLE tmp_bank2 SELECT b.rowid, b.amount, p.rowid as pid, - p.amount as pamount, - p.multicurrency_amount as pmulticurrencyamount, b.datec FROM llx_bank as b INNER JOIN llx_bank_url as bu ON bu.fk_bank=b.rowid AND bu.type = 'payment_supplier' INNER JOIN llx_paiementfourn as p ON bu.url_id = p.rowid WHERE p.multicurrency_amount <> 0 AND p.multicurrency_amount <> p.amount;
+-- UPDATE llx_bank as b SET b.amount_main_currency = (SELECT tb.pamount FROM tmp_bank2 as tb WHERE tb.rowid = b.rowid) WHERE b.amount_main_currency IS NULL AND b.rowid IN (SELECT rowid FROM tmp_bank2);
+
+-- Sequence to fix the content of llx_bank.amount_main_currency (sign was wrong with some version)
+-- UPDATE llx_bank as b SET b.amount_main_currency = -b.amount_main_currency WHERE b.amount IS NOT NULL AND b.amount_main_currency IS NOT NULL AND SIGN(b.amount_main_currency) <> SIGN(b.amount);
+

+ 1 - 1
htdocs/install/mysql/tables/llx_bank.sql

@@ -25,7 +25,7 @@ create table llx_bank
   datev           date,                      -- date de valeur
   dateo           date,                      -- date operation
   amount          double(24,8) NOT NULL default 0,		-- amount in the currency of the bank account
-  amount_main_currency double(24,8) NULL,				-- amount in the main currency of the company
+  amount_main_currency double(24,8) NULL,				-- amount in the main currency of the company when payment done in a bank account with a different currency
   label           varchar(255),
   fk_account      integer,
   fk_user_author  integer,

+ 1 - 1
htdocs/install/mysql/tables/llx_overwrite_trans.key.sql

@@ -17,5 +17,5 @@
 -- ===========================================================================
 
 
-ALTER TABLE llx_overwrite_trans ADD UNIQUE INDEX uk_overwrite_trans(lang, transkey);
+ALTER TABLE llx_overwrite_trans ADD UNIQUE INDEX uk_overwrite_trans(lang, transkey, entity);
 

+ 4 - 4
htdocs/projet/card.php

@@ -390,7 +390,7 @@ if (empty($reshook)) {
 			require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
 
 			$langs->load("other");
-			$upload_dir = $conf->project->dir_output;
+			$upload_dir = $conf->project->multidir_output[$object->entity];
 			$file = $upload_dir.'/'.GETPOST('file');
 			$ret = dol_delete_file($file, 0, 0, 0, $object);
 			if ($ret) {
@@ -1402,12 +1402,12 @@ if ($action == 'create' && $user->rights->projet->creer) {
 		 * Generated documents
 		 */
 		$filename = dol_sanitizeFileName($object->ref);
-		$filedir = $conf->project->dir_output."/".dol_sanitizeFileName($object->ref);
+		$filedir = $conf->project->multidir_output[$object->entity]."/".dol_sanitizeFileName($object->ref);
 		$urlsource = $_SERVER["PHP_SELF"]."?id=".$object->id;
 		$genallowed = ($user->rights->projet->lire && $userAccess > 0);
 		$delallowed = ($user->rights->projet->creer && $userWrite > 0);
 
-		print $formfile->showdocuments('project', $filename, $filedir, $urlsource, $genallowed, $delallowed, $object->model_pdf);
+		print $formfile->showdocuments('project', $filename, $filedir, $urlsource, $genallowed, $delallowed, $object->model_pdf, 1, 0, 0, 0, 0, '', '', '', '', '', $object);
 
 		print '</div><div class="fichehalfright">';
 
@@ -1426,7 +1426,7 @@ if ($action == 'create' && $user->rights->projet->creer) {
 	// Presend form
 	$modelmail = 'project';
 	$defaulttopic = 'SendProjectRef';
-	$diroutput = $conf->project->dir_output;
+	$diroutput = $conf->project->multidir_output[$object->entity];
 	$autocopy = 'MAIN_MAIL_AUTOCOPY_PROJECT_TO'; // used to know the automatic BCC to add
 	$trackid = 'proj'.$object->id;
 

+ 1 - 0
htdocs/projet/class/project.class.php

@@ -1347,6 +1347,7 @@ class Project extends CommonObject
 		// Initialise parameters
 		$this->id = 0;
 		$this->ref = 'SPECIMEN';
+		$this->entity = $conf->entity;
 		$this->specimen = 1;
 		$this->socid = 1;
 		$this->date_c = $now;

+ 3 - 1
htdocs/projet/class/task.class.php

@@ -198,7 +198,7 @@ class Task extends CommonObjectLine
 		$sql .= ", progress";
 		$sql .= ", budget_amount";
 		$sql .= ") VALUES (";
-		$sql .= ((int) $conf->entity);
+		$sql .= (!empty($this->entity) ? (int) $this->entity : (int) $conf->entity);
 		$sql .= ", ".((int) $this->fk_project);
 		$sql .= ", ".(!empty($this->ref) ? "'".$this->db->escape($this->ref)."'" : 'null');
 		$sql .= ", ".((int) $this->fk_task_parent);
@@ -274,6 +274,7 @@ class Task extends CommonObjectLine
 		$sql = "SELECT";
 		$sql .= " t.rowid,";
 		$sql .= " t.ref,";
+		$sql .= " t.entity,";
 		$sql .= " t.fk_projet as fk_project,";
 		$sql .= " t.fk_task_parent,";
 		$sql .= " t.label,";
@@ -318,6 +319,7 @@ class Task extends CommonObjectLine
 
 				$this->id = $obj->rowid;
 				$this->ref = $obj->ref;
+				$this->entity = $obj->entity;
 				$this->fk_project = $obj->fk_project;
 				$this->fk_task_parent = $obj->fk_task_parent;
 				$this->label = $obj->label;

+ 2 - 2
htdocs/projet/document.php

@@ -49,7 +49,7 @@ if (!empty($conf->global->PROJECT_ALLOW_COMMENT_ON_PROJECT) && method_exists($ob
 }
 
 if ($id > 0 || !empty($ref)) {
-	$upload_dir = $conf->project->dir_output."/".dol_sanitizeFileName($object->ref);
+	$upload_dir = $conf->project->multidir_output[$object->entity]."/".dol_sanitizeFileName($object->ref);
 }
 
 // Get parameters
@@ -109,7 +109,7 @@ llxHeader('', $title, $help_url);
 $form = new Form($db);
 
 if ($object->id > 0) {
-	$upload_dir = $conf->project->dir_output.'/'.dol_sanitizeFileName($object->ref);
+	$upload_dir = $conf->project->multidir_output[$object->entity].'/'.dol_sanitizeFileName($object->ref);
 
 	// To verify role of users
 	//$userAccess = $object->restrictedProjectArea($user,'read');

+ 1 - 0
htdocs/projet/tasks.php

@@ -336,6 +336,7 @@ if ($action == 'createtask' && $user->rights->projet->creer) {
 			$task = new Task($db);
 
 			$task->fk_project = $projectid;
+			$task->entity = $object->entity; // Task have the same entity of project
 			$task->ref = $taskref;
 			$task->label = $label;
 			$task->description = $description;

+ 1 - 1
htdocs/projet/tasks/document.php

@@ -110,7 +110,7 @@ if ($id > 0 || !empty($ref)) {
 
 	$object->project = clone $projectstatic;
 
-	$upload_dir = $conf->project->dir_output.'/'.dol_sanitizeFileName($projectstatic->ref).'/'.dol_sanitizeFileName($object->ref);
+	$upload_dir = $conf->project->multidir_output[$projectstatic->entity].'/'.dol_sanitizeFileName($projectstatic->ref).'/'.dol_sanitizeFileName($object->ref);
 }
 
 include DOL_DOCUMENT_ROOT.'/core/actions_linkedfiles.inc.php';

+ 4 - 3
htdocs/projet/tasks/time.php

@@ -53,6 +53,7 @@ $toselect = GETPOST('toselect', 'array'); // Array of ids of elements selected i
 $contextpage = GETPOST('contextpage', 'aZ') ?GETPOST('contextpage', 'aZ') : 'timespentlist'; // To manage different context of search
 $backtopage = GETPOST('backtopage', 'alpha'); // Go back to a dedicated page
 $optioncss	= GETPOST('optioncss', 'alpha');
+$mode       = GETPOST('mode', 'alpha');
 
 $id			= GETPOST('id', 'int');
 $projectid	= GETPOST('projectid', 'int');
@@ -351,10 +352,10 @@ if (($action == 'updateline' || $action == 'updatesplitline') && !$cancel && $us
 	}
 }
 
-if ($action == 'confirm_deleteline' && $confirm == "yes" && $user->rights->projet->supprimer) {
+if ($action == 'confirm_deleteline' && $confirm == "yes" && ($user->hasRight('projet', 'time') || $user->hasRight('projet', 'all', 'creer'))) {
 	$object->fetchTimeSpent(GETPOST('lineid', 'int'));	// load properties like $object->timespent_id
 
-	if (in_array($object->timespent_fk_user, $childids) || $user->rights->projet->all->creer) {
+	if (in_array($object->timespent_fk_user, $childids) || $user->hasRight('projet', 'all', 'creer')) {
 		$result = $object->delTimeSpent($user);	// delete line with $object->timespent_id
 
 		if ($result < 0) {
@@ -1981,7 +1982,7 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser
 				print ' ';
 				print '<input type="submit" class="button buttongen margintoponlyshort marginbottomonlyshort button-cancel" name="cancel" value="'.$langs->trans("Cancel").'">';
 			} elseif ($user->hasRight('projet', 'time') || $user->hasRight('projet', 'all', 'creer')) {	 // Read project and enter time consumed on assigned tasks
-				if ($task_time->fk_user == $user->id || in_array($task_time->fk_user, $childids) || $user->hasRight('projet', 'all', 'creer')) {
+				if (in_array($task_time->fk_user, $childids) || $user->hasRight('projet', 'all', 'creer')) {
 					if (getDolGlobalString('MAIN_FEATURES_LEVEL') >= 2) {
 						print '&nbsp;';
 						print '<a class="reposition" href="'.$_SERVER["PHP_SELF"].'?action=splitline&token='.newToken().'&lineid='.$task_time->rowid.$param.((empty($id) || $tab == 'timespent') ? '&tab=timespent' : '').'">';

+ 0 - 3
htdocs/public/ticket/create_ticket.php

@@ -26,9 +26,6 @@
 if (!defined('NOREQUIREUSER')) {
 	define('NOREQUIREUSER', '1');
 }*/
-if (!defined('NOTOKENRENEWAL')) {
-	define('NOTOKENRENEWAL', '1');
-}
 if (!defined('NOREQUIREMENU')) {
 	define('NOREQUIREMENU', '1');
 }

+ 1 - 1
htdocs/reception/card.php

@@ -889,7 +889,7 @@ if ($action == 'create') {
 				if ($objectsrc->fetch_optionals() > 0) {
 					$recept->array_options = array_merge($recept->array_options, $objectsrc->array_options);
 				}
-				print $object->showOptionals($extrafields, 'create', $parameters);
+				print $recept->showOptionals($extrafields, 'create', $parameters);
 			}
 
 			// Incoterms

+ 1 - 0
htdocs/user/list.php

@@ -344,6 +344,7 @@ $morehtmlright = "";
 // Build and execute select
 // --------------------------------------------------------------------
 $sql = "SELECT DISTINCT u.rowid, u.lastname, u.firstname, u.admin, u.fk_soc, u.login, u.office_phone, u.user_mobile, u.email, u.api_key, u.accountancy_code, u.gender, u.employee, u.photo,";
+$sql .= " u.fk_user,";
 $sql .= " u.salary, u.datelastlogin, u.datepreviouslogin,";
 $sql .= " u.ldap_sid, u.statut as status, u.entity,";
 $sql .= " u.tms as date_update, u.datec as date_creation,";

+ 5 - 0
test/phpunit/AllTests.php

@@ -219,6 +219,7 @@ class AllTests
 		require_once dirname(__FILE__).'/AccountingAccountTest.php';
 		$suite->addTestSuite('AccountingAccountTest');
 
+		// Rest
 		require_once dirname(__FILE__).'/RestAPIUserTest.php';
 		$suite->addTestSuite('RestAPIUserTest');
 		require_once dirname(__FILE__).'/RestAPIDocumentTest.php';
@@ -270,6 +271,10 @@ class AllTests
 		require_once dirname(__FILE__).'/EmailCollectorTest.php';
 		$suite->addTestSuite('EmailCollectorTest');
 
+		// Website
+		require_once dirname(__FILE__).'/WebsiteTest.php';
+		$suite->addTestSuite('WebsiteTest');
+
 		return $suite;
 	}
 }

+ 18 - 0
test/phpunit/Website.class.php → test/phpunit/WebsiteTest.php

@@ -175,4 +175,22 @@ class WebsiteTest extends PHPUnit\Framework\TestCase
 		// We must found no line (so code should be KO). If we found somethiing, it means there is a SQL injection of the 1=1
 		$this->assertEquals($res['code'], 'KO');
 	}
+
+	/**
+	 * testDolStripPhpCode
+	 *
+	 * @return	void
+	 */
+	public function testDolStripPhpCode()
+	{
+		global $db;
+
+		$s = "abc\n<?php echo 'def'\n// comment\n ?>ghi";
+		$result = dolStripPhpCode($s);
+		$this->assertEquals("abc\n<span phptag></span>ghi", $result);
+
+		$s = "abc\n<?PHP echo 'def'\n// comment\n ?>ghi";
+		$result = dolStripPhpCode($s);
+		$this->assertEquals("abc\n<span phptag></span>ghi", $result);
+	}
 }