Browse Source

Merge branch 'Dolibarr:develop' into NEW-imap-by-oauth2

fboitel 2 years ago
parent
commit
6f05b20926
100 changed files with 2297 additions and 1674 deletions
  1. 1 1
      COPYRIGHT
  2. 2 2
      htdocs/accountancy/index.php
  3. 32 28
      htdocs/adherents/card.php
  4. 16 1
      htdocs/adherents/class/adherent.class.php
  5. 2 1
      htdocs/adherents/type.php
  6. 6 4
      htdocs/admin/dict.php
  7. 110 6
      htdocs/admin/mails.php
  8. 1 0
      htdocs/admin/mails_senderprofile_list.php
  9. 8 3
      htdocs/admin/modules.php
  10. 53 8
      htdocs/admin/oauth.php
  11. 40 31
      htdocs/admin/oauthlogintokens.php
  12. 19 16
      htdocs/barcode/printsheet.php
  13. 4 1
      htdocs/categories/class/api_categories.class.php
  14. 2 1
      htdocs/categories/class/categorie.class.php
  15. 4 1
      htdocs/categories/viewcat.php
  16. 3 3
      htdocs/comm/action/card.php
  17. 1 1
      htdocs/comm/action/index.php
  18. 1 1
      htdocs/comm/action/pertype.php
  19. 1 1
      htdocs/comm/action/peruser.php
  20. 1 2
      htdocs/comm/multiprix.php
  21. 4 4
      htdocs/comm/propal/card.php
  22. 3 3
      htdocs/commande/card.php
  23. 5 1
      htdocs/compta/bank/bankentries_list.php
  24. 4 1
      htdocs/compta/bank/card.php
  25. 6 6
      htdocs/compta/facture/card.php
  26. 31 7
      htdocs/compta/paiement/cheque/card.php
  27. 1 6
      htdocs/compta/paiement_charge.php
  28. 1 1
      htdocs/conf/conf.php.example
  29. 1 1
      htdocs/contact/perso.php
  30. 1 2
      htdocs/contrat/class/contrat.class.php
  31. 2 2
      htdocs/contrat/list.php
  32. 8 3
      htdocs/core/actions_sendmails.inc.php
  33. 122 12
      htdocs/core/class/CMailFile.class.php
  34. 3 3
      htdocs/core/class/comment.class.php
  35. 24 15
      htdocs/core/class/commonobject.class.php
  36. 18 7
      htdocs/core/class/html.form.class.php
  37. 5 5
      htdocs/core/class/html.formmail.class.php
  38. 8 4
      htdocs/core/class/html.formticket.class.php
  39. 35 8
      htdocs/core/class/smtps.class.php
  40. 1 0
      htdocs/core/class/translate.class.php
  41. 4 4
      htdocs/core/class/utils_diff.class.php
  42. 3 1
      htdocs/core/filemanagerdol/browser/default/browser.php
  43. 2 0
      htdocs/core/filemanagerdol/browser/default/frmactualfolder.php
  44. 2 0
      htdocs/core/filemanagerdol/browser/default/frmcreatefolder.php
  45. 2 0
      htdocs/core/filemanagerdol/browser/default/frmfolders.php
  46. 2 0
      htdocs/core/filemanagerdol/browser/default/frmresourceslist.php
  47. 2 0
      htdocs/core/filemanagerdol/browser/default/frmupload.php
  48. 0 131
      htdocs/core/filemanagerdol/connectors/php/basexml.php
  49. 0 325
      htdocs/core/filemanagerdol/connectors/php/commands.php
  50. 0 0
      htdocs/core/filemanagerdol/connectors/php/config.inc.php
  51. 1048 0
      htdocs/core/filemanagerdol/connectors/php/connector.lib.php
  52. 4 7
      htdocs/core/filemanagerdol/connectors/php/connector.php
  53. 0 438
      htdocs/core/filemanagerdol/connectors/php/io.php
  54. 0 75
      htdocs/core/filemanagerdol/connectors/php/upload.php
  55. 0 218
      htdocs/core/filemanagerdol/connectors/php/util.php
  56. 1 1
      htdocs/core/lib/admin.lib.php
  57. 29 0
      htdocs/core/lib/ftp.lib.php
  58. 17 9
      htdocs/core/lib/functions.lib.php
  59. 24 5
      htdocs/core/lib/oauth.lib.php
  60. 59 61
      htdocs/core/modules/facture/doc/pdf_sponge.modules.php
  61. 193 0
      htdocs/core/modules/oauth/generic_oauthcallback.php
  62. 2 2
      htdocs/core/modules/oauth/github_oauthcallback.php
  63. 1 1
      htdocs/core/modules/oauth/google_oauthcallback.php
  64. 1 1
      htdocs/core/modules/oauth/stripelive_oauthcallback.php
  65. 1 1
      htdocs/core/modules/oauth/stripetest_oauthcallback.php
  66. 0 1
      htdocs/core/modules/printing/printgcp.modules.php
  67. 3 1
      htdocs/core/triggers/interface_50_modTicket_TicketEmail.class.php
  68. 1 1
      htdocs/document.php
  69. 1 1
      htdocs/emailcollector/class/emailcollector.class.php
  70. 2 2
      htdocs/fichinter/class/fichinter.class.php
  71. 36 4
      htdocs/ftp/index.php
  72. 2 2
      htdocs/imports/import.php
  73. 85 80
      htdocs/includes/jquery/js/jquery-ui.js
  74. 1 1
      htdocs/includes/jquery/js/jquery-ui.min.js
  75. 3 3
      htdocs/includes/tecnickcom/tcpdf/tcpdf_barcodes_1d.php
  76. 4 4
      htdocs/index.php
  77. BIN
      htdocs/install/doctemplates/websites/website_template-style01.png
  78. BIN
      htdocs/install/doctemplates/websites/website_template-style01.zip
  79. BIN
      htdocs/install/doctemplates/websites/website_template-style02.zip
  80. 5 0
      htdocs/install/mysql/migration/16.0.0-17.0.0.sql
  81. 1 0
      htdocs/install/mysql/tables/llx_adherent.sql
  82. 1 0
      htdocs/install/mysql/tables/llx_product.sql
  83. 15 6
      htdocs/knowledgemanagement/class/api_knowledgemanagement.class.php
  84. 18 0
      htdocs/knowledgemanagement/class/knowledgerecord.class.php
  85. 7 1
      htdocs/langs/en_US/admin.lang
  86. 1 0
      htdocs/langs/en_US/errors.lang
  87. 5 2
      htdocs/langs/en_US/oauth.lang
  88. 3 0
      htdocs/langs/en_US/other.lang
  89. 1 0
      htdocs/langs/en_US/website.lang
  90. 7 0
      htdocs/langs/fr_FR/admin.lang
  91. 3 2
      htdocs/main.inc.php
  92. 4 4
      htdocs/mrp/class/mo.class.php
  93. 39 30
      htdocs/mrp/mo_production.php
  94. 8 6
      htdocs/mrp/tpl/originproductline.tpl.php
  95. 3 3
      htdocs/product/card.php
  96. 5 5
      htdocs/product/class/api_products.class.php
  97. 36 26
      htdocs/product/class/product.class.php
  98. 5 2
      htdocs/product/fournisseurs.php
  99. 4 4
      htdocs/product/note.php
  100. 1 0
      htdocs/product/popuprop.php

+ 1 - 1
COPYRIGHT

@@ -52,7 +52,7 @@ Ace                    1.4.14        BSD                         Yes
 ChartJS                3.7.1         MIT License                 Yes             JS library for graph
 CKEditor               4.18          LGPL-2.1+                   Yes             Editor WYSIWYG
 jQuery                 3.6.0         MIT License                 Yes             JS library
-jQuery UI              1.13.1        GPL and MIT License         Yes             JS library plugin UI
+jQuery UI              1.13.2        GPL and MIT License         Yes             JS library plugin UI
 jQuery select2         4.0.13        GPL and Apache License      Yes             JS library plugin for sexier multiselect. Warning: 4.0.6+ create troubles without patching css
 jQuery blockUI         2.70.0        GPL and MIT License         Yes             JS library plugin blockUI (to use ajax popups)
 jQuery Colorpicker     1.1           MIT License                 Yes             JS library for color picker for a defined list of colors

+ 2 - 2
htdocs/accountancy/index.php

@@ -49,7 +49,7 @@ if (empty($user->rights->accounting->mouvements->lire)) {
 if (empty($conf->comptabilite->enabled) && empty($conf->accounting->enabled) && empty($conf->asset->enabled) && empty($conf->intracommreport->enabled)) {
 	accessforbidden();
 }
-if (empty($user->rights->compta->resultat->lire) && empty($user->rights->accounting->comptarapport->lire) && empty($user->rights->accounting->mouvements->lire) && empty($user->rights->asset->read) && empty($user->rights->intracommreport->read)) {
+if (empty($user->hasRight('compta', 'resultat', 'lire')) && empty($user->hasRight('accounting', 'comptarapport', 'lire')) && empty($user->hasRight('accounting', 'mouvements', 'lire')) && empty($user->hasRight('asset', 'read')) && empty($user->hasRight('intracommreport', 'read'))) {
 	accessforbidden();
 }
 
@@ -117,7 +117,7 @@ if (!empty($conf->global->INVOICE_USE_SITUATION) && $conf->global->INVOICE_USE_S
 	print '<div class="'.($helpisexpanded ? '' : 'hideobject').'" id="idfaq">'; // hideobject is to start hidden
 	print "<br>\n";
 	print '<span class="opacitymedium">'.$langs->trans("AccountancyAreaDescIntro")."</span><br>\n";
-	if (!empty($user->rights->accounting->chartofaccount)) {
+	if ($user->hasRight('accounting', 'chartofaccount')) {
 		print "<br>\n"; print "<br>\n";
 
 		print load_fiche_titre('<span class="fa fa-calendar-check-o"></span> '.$langs->trans("AccountancyAreaDescActionOnce"), '', '')."\n";

+ 32 - 28
htdocs/adherents/card.php

@@ -40,6 +40,7 @@ require_once DOL_DOCUMENT_ROOT.'/adherents/class/subscription.class.php';
 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
 require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
 
@@ -277,7 +278,7 @@ if (empty($reshook)) {
 		}
 		// Create new object
 		if ($result > 0 && !$error) {
-			$object->oldcopy = clone $object;
+			$object->oldcopy = dol_clone($object);
 
 			// Change values
 			$object->civility_id = trim(GETPOST("civility_id", 'alphanohtml'));
@@ -311,8 +312,8 @@ if (empty($reshook)) {
 			//$object->twitter     = trim(GETPOST("twitter", 'alpha'));
 			//$object->facebook    = trim(GETPOST("facebook", 'alpha'));
 			//$object->linkedin    = trim(GETPOST("linkedin", 'alpha'));
-			$object->birth       = $birthdate;
-
+			$object->birth		= $birthdate;
+			$object->default_lang = GETPOST('default_lang', 'alpha');
 			$object->typeid      = GETPOST("typeid", 'int');
 			//$object->note        = trim(GETPOST("comment","alpha"));
 			$object->morphy      = GETPOST("morphy", 'alpha');
@@ -457,6 +458,7 @@ if (empty($reshook)) {
 
 		$userid = GETPOST("userid", 'int');
 		$socid = GETPOST("socid", 'int');
+		$default_lang = GETPOST('default_lang', 'alpha');
 
 		$object->civility_id = $civility_id;
 		$object->firstname   = $firstname;
@@ -498,7 +500,7 @@ if (empty($reshook)) {
 		$object->user_id     = $userid;
 		$object->socid = $socid;
 		$object->public      = $public;
-
+		$object->default_lang = $default_lang;
 		// Fill array 'array_options' with data from add form
 		$ret = $extrafields->setOptionalsFromPost(null, $object);
 		if ($ret < 0) {
@@ -580,36 +582,16 @@ if (empty($reshook)) {
 				$id = $object->id;
 			} else {
 				$db->rollback();
+				setEventMessages($object->error, $object->errors, 'errors');
 
-				if ($object->error) {
-					setEventMessages($object->error, $object->errors, 'errors');
-				} else {
-					setEventMessages($object->error, $object->errors, 'errors');
-				}
 			}
+			
 			// Auto-create thirdparty on member creation
 			if (!empty($conf->global->ADHERENT_DEFAULT_CREATE_THIRDPARTY)) {
 				if ($result > 0) {
-					// User creation
+					// Create third party out of a member
 					$company = new Societe($db);
-
-					$companyalias = '';
-					$fullname = $object->getFullName($langs);
-
-					if ($object->morphy == 'mor') {
-						$companyname = $object->company;
-						if (!empty($fullname)) {
-							$companyalias = $fullname;
-						}
-					} else {
-						$companyname = $fullname;
-						if (!empty($object->company)) {
-							$companyalias = $object->company;
-						}
-					}
-
-					$result = $company->create_from_member($object, $companyname, $companyalias);
-
+					$result = $company->create_from_member($object);
 					if ($result < 0) {
 						$langs->load("errors");
 						setEventMessages($langs->trans($company->error), null, 'errors');
@@ -899,6 +881,7 @@ if (empty($reshook)) {
 
 $form = new Form($db);
 $formfile = new FormFile($db);
+$formadmin = new FormAdmin($db);
 $formcompany = new FormCompany($db);
 
 $title = $langs->trans("Member")." - ".$langs->trans("Card");
@@ -1363,6 +1346,14 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 		print $form->selectDate(($object->birth ? $object->birth : -1), 'birth', '', '', 1, 'formsoc');
 		print "</td></tr>\n";
 
+		// Default language
+		if (!empty($conf->global->MAIN_MULTILANGS)) {
+			print '<tr><td>'.$form->editfieldkey('DefaultLang', 'default_lang', '', $object, 0).'</td><td colspan="3">'."\n";
+			print img_picto('', 'language').$formadmin->select_language($object->default_lang, 'default_lang', 0, 0, 1);
+			print '</td>';
+			print '</tr>';
+		}
+
 		// Public profil
 		print "<tr><td>".$langs->trans("Public")."</td><td>\n";
 		print $form->selectyesno("public", (GETPOSTISSET("public") ? GETPOST("public", 'alphanohtml', 2) : $object->public), 1);
@@ -1803,6 +1794,19 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 		// Birth Date
 		print '<tr><td class="titlefield">'.$langs->trans("DateOfBirth").'</td><td class="valeur">'.dol_print_date($object->birth, 'day').'</td></tr>';
 
+		// Default language
+		if (!empty($conf->global->MAIN_MULTILANGS)) {
+			require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
+			print '<tr><td>'.$langs->trans("DefaultLang").'</td><td>';
+			//$s=picto_from_langcode($object->default_lang);
+			//print ($s?$s.' ':'');
+			$langs->load("languages");
+			$labellang = ($object->default_lang ? $langs->trans('Language_'.$object->default_lang) : '');
+			print picto_from_langcode($object->default_lang, 'class="paddingrightonly saturatemedium opacitylow"');
+			print $labellang;
+			print '</td></tr>';
+		}
+
 		// Public
 		print '<tr><td>'.$langs->trans("Public").'</td><td class="valeur">'.yn($object->public).'</td></tr>';
 

+ 16 - 1
htdocs/adherents/class/adherent.class.php

@@ -215,6 +215,12 @@ class Adherent extends CommonObject
 	 */
 	public $public;
 
+	/**
+	 * Default language code of member (en_US, ...)
+	 * @var string
+	 */
+	public $default_lang;
+
 	/**
 	 * @var string photo of member
 	 */
@@ -328,6 +334,7 @@ class Adherent extends CommonObject
 		'photo' => array('type' => 'varchar(255)', 'label' => 'Photo', 'enabled' => 1, 'visible' => -1, 'position' => 135),
 		'public' => array('type' => 'smallint(6)', 'label' => 'Public', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 145),
 		'datefin' => array('type' => 'datetime', 'label' => 'DateEnd', 'enabled' => 1, 'visible' => -1, 'position' => 150),
+		'default_lang' =>array('type'=>'varchar(6)', 'label'=>'Default lang', 'enabled'=>1, 'visible'=>-1, 'position'=> 153),
 		'note_private' => array('type' => 'text', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 155),
 		'note_public' => array('type' => 'text', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 160),
 		'datevalid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 165),
@@ -710,9 +717,11 @@ class Adherent extends CommonObject
 		$sql .= ", photo = ".($this->photo ? "'".$this->db->escape($this->photo)."'" : "null");
 		$sql .= ", public = '".$this->db->escape($this->public)."'";
 		$sql .= ", statut = ".$this->db->escape($this->statut);
+		$sql .= ", default_lang = ".(!empty($this->default_lang) ? "'".$this->db->escape($this->default_lang)."'" : "null");
 		$sql .= ", fk_adherent_type = ".$this->db->escape($this->typeid);
 		$sql .= ", morphy = '".$this->db->escape($this->morphy)."'";
 		$sql .= ", birth = ".($this->birth ? "'".$this->db->idate($this->birth)."'" : "null");
+
 		if ($this->datefin) {
 			$sql .= ", datefin = '".$this->db->idate($this->datefin)."'"; // Must be modified only when deleting a subscription
 		}
@@ -833,6 +842,8 @@ class Adherent extends CommonObject
 						$luser->office_phone = $this->phone;
 						$luser->user_mobile = $this->phone_mobile;
 
+						$luser->lang = $this->default_lang;
+
 						$luser->fk_member = $this->id;
 
 						$result = $luser->update($user, 0, 1, 1); // Use nosync to 1 to avoid cyclic updates
@@ -868,6 +879,7 @@ class Adherent extends CommonObject
 						$lthirdparty->state_id = $this->state_id;
 						$lthirdparty->country_id = $this->country_id;
 						//$lthirdparty->phone_mobile=$this->phone_mobile;
+						$lthirdparty->default_lang = $this->default_lang;
 
 						$result = $lthirdparty->update($this->fk_soc, $user, 0, 1, 1, 'update'); // Use sync to 0 to avoid cyclic updates
 
@@ -1314,7 +1326,7 @@ class Adherent extends CommonObject
 		$sql .= " d.photo, d.fk_adherent_type, d.morphy, d.entity,";
 		$sql .= " d.datec as datec,";
 		$sql .= " d.tms as datem,";
-		$sql .= " d.datefin as datefin,";
+		$sql .= " d.datefin as datefin, d.default_lang,";
 		$sql .= " d.birth as birthday,";
 		$sql .= " d.datevalid as datev,";
 		$sql .= " d.country,";
@@ -1407,6 +1419,8 @@ class Adherent extends CommonObject
 				$this->date_validation = $this->db->jdate($obj->datev);
 				$this->birth = $this->db->jdate($obj->birthday);
 
+				$this->default_lang = $obj->default_lang;
+
 				$this->note_private = $obj->note_private;
 				$this->note_public = $obj->note_public;
 				$this->morphy = $obj->morphy;
@@ -2556,6 +2570,7 @@ class Adherent extends CommonObject
 
 		$this->datefin = $now;
 		$this->datevalid = $now;
+		$this->default_lang = '';
 
 		$this->typeid = 1; // Id type adherent
 		$this->type = 'Type adherent'; // Libelle type adherent

+ 2 - 1
htdocs/adherents/type.php

@@ -170,7 +170,8 @@ if ($action == 'add' && $user->rights->adherent->configurer) {
 if ($action == 'update' && $user->rights->adherent->configurer) {
 	$object->fetch($rowid);
 
-	$object->oldcopy = clone $object;
+	$object->oldcopy = dol_clone($object);
+
 	$object->label= trim($label);
 	$object->morphy	= trim($morphy);
 	$object->status	= (int) $status;

+ 6 - 4
htdocs/admin/dict.php

@@ -1220,14 +1220,16 @@ if ($id > 0) {
 		$sql .= natural_search("code_iso", $search_code);
 	} elseif ($search_code != '' && $id == 28) {
 		$sql .= natural_search("h.code", $search_code);
-	} elseif ($search_code != '' && $id == 32) {
+	} elseif ($search_code != '' && ($id == 7 || $id == 32)) {
 		$sql .= natural_search("a.code", $search_code);
 	} elseif ($search_code != '' && $id == 3) {
 		$sql .= natural_search("r.code_region", $search_code);
-	} elseif ($search_code != '' && $id == 7) {
-		$sql .= natural_search("a.code", $search_code);
-	} elseif ($search_code != '' && $id == 10) {
+	} elseif ($search_code != '' && ($id == 8 || $id == 10)) {
 		$sql .= natural_search("t.code", $search_code);
+	} elseif ($search_code != '' && $id == 1) {
+		$sql .= natural_search("f.code", $search_code);
+	} elseif ($search_code != '' && $id == 2) {
+		$sql .= natural_search("d.code_departement", $search_code);
 	} elseif ($search_code != '' && $id != 9) {
 		$sql .= natural_search("code", $search_code);
 	}

+ 110 - 6
htdocs/admin/mails.php

@@ -88,7 +88,15 @@ if ($action == 'update' && !$cancel) {
 		dolibarr_set_const($db, "MAIN_MAIL_SMTP_PORT", GETPOST("MAIN_MAIL_SMTP_PORT", 'int'), 'chaine', 0, '', $conf->entity);
 		dolibarr_set_const($db, "MAIN_MAIL_SMTP_SERVER", GETPOST("MAIN_MAIL_SMTP_SERVER", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
 		dolibarr_set_const($db, "MAIN_MAIL_SMTPS_ID", GETPOST("MAIN_MAIL_SMTPS_ID", 'alphanohtml'), 'chaine', 0, '', $conf->entity);
-		dolibarr_set_const($db, "MAIN_MAIL_SMTPS_PW", GETPOST("MAIN_MAIL_SMTPS_PW", 'none'), 'chaine', 0, '', $conf->entity);
+		if (GETPOSTISSET("MAIN_MAIL_SMTPS_PW")) {
+			dolibarr_set_const($db, "MAIN_MAIL_SMTPS_PW", GETPOST("MAIN_MAIL_SMTPS_PW", 'none'), 'chaine', 0, '', $conf->entity);
+		}
+		if (GETPOSTISSET("MAIN_MAIL_SMTPS_AUTH_TYPE")) {
+			dolibarr_set_const($db, "MAIN_MAIL_SMTPS_AUTH_TYPE", GETPOST("MAIN_MAIL_SMTPS_AUTH_TYPE", 'chaine'), 'chaine', 0, '', $conf->entity);
+		}
+		if (GETPOSTISSET("MAIN_MAIL_SMTPS_OAUTH_SERVICE")) {
+			dolibarr_set_const($db, "MAIN_MAIL_SMTPS_OAUTH_SERVICE", GETPOST("MAIN_MAIL_SMTPS_OAUTH_SERVICE", 'chaine'), 'chaine', 0, '', $conf->entity);
+		}
 		dolibarr_set_const($db, "MAIN_MAIL_EMAIL_TLS", GETPOST("MAIN_MAIL_EMAIL_TLS", 'int'), 'chaine', 0, '', $conf->entity);
 		dolibarr_set_const($db, "MAIN_MAIL_EMAIL_STARTTLS", GETPOST("MAIN_MAIL_EMAIL_STARTTLS", 'int'), 'chaine', 0, '', $conf->entity);
 		dolibarr_set_const($db, "MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED", GETPOST("MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED", 'int'), 'chaine', 0, '', $conf->entity);
@@ -171,6 +179,24 @@ if (version_compare(phpversion(), '7.0', '>=')) {
 	$listofmethods['swiftmailer'] = 'Swift Mailer socket library';
 }
 
+// List of oauth services
+$oauthservices = array();
+
+foreach ($conf->global as $key => $val) {
+	if (!empty($val) && preg_match('/^OAUTH_.*_ID$/', $key)) {
+		$key = preg_replace('/^OAUTH_/', '', $key);
+		$key = preg_replace('/_ID$/', '', $key);
+		if (preg_match('/^.*-/', $key)) {
+			$name = preg_replace('/^.*-/', '', $key);
+		} else {
+			$name = $langs->trans("NoName");
+		}
+		$provider = preg_replace('/-.*$/', '', $key);
+		$provider = ucfirst(strtolower($provider));
+
+		$oauthservices[$key] = $name." (".$provider.")";
+	}
+}
 
 if ($action == 'edit') {
 	if ($conf->use_javascript_ajax) {
@@ -195,6 +221,7 @@ if ($action == 'edit') {
                             jQuery("#MAIN_MAIL_EMAIL_DKIM_PRIVATE_KEY").prop("disabled", true);
                             jQuery(".smtp_method").hide();
                             jQuery(".dkim").hide();
+                            jQuery(".smtp_auth_method").hide();
                             ';
 		if ($linuxlike) {
 			print '
@@ -237,6 +264,7 @@ if ($action == 'edit') {
 			                jQuery("#smtp_port_mess").hide();
                             jQuery(".smtp_method").show();
 							jQuery(".dkim").hide();
+                            jQuery(".smtp_auth_method").show();
 						}
                         if (jQuery("#MAIN_MAIL_SENDMODE").val()==\'swiftmailer\')
                         {
@@ -264,12 +292,34 @@ if ($action == 'edit') {
                             jQuery("#smtp_port_mess").hide();
 							jQuery(".smtp_method").show();
                             jQuery(".dkim").show();
+							jQuery(".smtp_auth_method").show();
                         }
                     }
+					function change_smtp_auth_method() {
+						console.log(jQuery("#radio_pw").prop("checked"));
+						if (jQuery("#MAIN_MAIL_SENDMODE").val()==\'smtps\' && jQuery("#radio_oauth").prop("checked")) {
+							jQuery(".smtp_oauth_service").show();
+							jQuery(".smtp_pw").hide();
+						} else if (jQuery("#MAIN_MAIL_SENDMODE").val()==\'swiftmailer\' && jQuery("#radio_oauth").prop("checked")) {
+							jQuery(".smtp_oauth_service").show();
+							jQuery(".smtp_pw").hide();
+						} else if(jQuery("#MAIN_MAIL_SENDMODE").val()==\'mail\'){
+							jQuery(".smtp_oauth_service").hide();
+							jQuery(".smtp_pw").hide();
+						} else {
+							jQuery(".smtp_oauth_service").hide();
+							jQuery(".smtp_pw").show();
+						}
+					}
                     initfields();
+					change_smtp_auth_method();
                     jQuery("#MAIN_MAIL_SENDMODE").change(function() {
                         initfields();
+						change_smtp_auth_method();
                     });
+					jQuery("#radio_pw, #radio_oauth").change(function() {
+						change_smtp_auth_method();
+					});
                     jQuery("#MAIN_MAIL_EMAIL_TLS").change(function() {
 						if (jQuery("#MAIN_MAIL_EMAIL_TLS").val() == 1)
 							jQuery("#MAIN_MAIL_EMAIL_STARTTLS").val(0);
@@ -413,10 +463,29 @@ if ($action == 'edit') {
 		print '</td></tr>';
 	}
 
+
+	// OAUTH
+	if (!empty($conf->use_javascript_ajax) || (isset($conf->global->MAIN_MAIL_SENDMODE) && in_array($conf->global->MAIN_MAIL_SENDMODE, array('smtps', 'swiftmailer')))) {
+		print '<tr class="oddeven smtp_auth_method"><td>'.$langs->trans("MAIN_MAIL_SMTPS_AUTH_TYPE").'</td><td>';
+		if (empty($conf->multicompany->enabled) || ($user->admin && !$user->entity)) {
+			print '<input type="radio" id="radio_pw" name="MAIN_MAIL_SMTPS_AUTH_TYPE" value="LOGIN"'.(getDolGlobalString('MAIN_MAIL_SMTPS_AUTH_TYPE') == 'LOGIN' ? ' checked' : '').'> ';
+			print '<label for="radio_pw" >'.$langs->trans("UsePassword").'</label>';
+			print '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
+			print '<input type="radio" id="radio_oauth" name="MAIN_MAIL_SMTPS_AUTH_TYPE" value="XOAUTH2"'.(getDolGlobalString('MAIN_MAIL_SMTPS_AUTH_TYPE') == 'XOAUTH2' ? ' checked' : '').'> ';
+			print '<label for="radio_oauth" >'.$form->textwithpicto($langs->trans("UseOauth"), $langs->trans("OauthNotAvailableForAllAndHadToBeCreatedBefore")).'</label>';
+		} else {
+			$value = getDolGlobalString('MAIN_MAIL_SMTPS_AUTH_TYPE', 'LOGIN');
+			$htmltext = $langs->trans("ContactSuperAdminForChange");
+			print $form->textwithpicto($langs->trans("MAIN_MAIL_SMTPS_AUTH_TYPE"), $htmltext, 1, 'superadmin');
+			print '<input type="hidden" id="MAIN_MAIL_SMTPS_AUTH_TYPE" name="MAIN_MAIL_SMTPS_AUTH_TYPE" value="'.$value.'">';
+		}
+		print '</td></tr>';
+	}
+
 	// PW
 	if (!empty($conf->use_javascript_ajax) || (isset($conf->global->MAIN_MAIL_SENDMODE) && in_array($conf->global->MAIN_MAIL_SENDMODE, array('smtps', 'swiftmailer')))) {
 		$mainsmtppw = (!empty($conf->global->MAIN_MAIL_SMTPS_PW) ? $conf->global->MAIN_MAIL_SMTPS_PW : '');
-		print '<tr class="drag drop oddeven"><td>';
+		print '<tr class="drag drop oddeven smtp_pw"><td>';
 		print $form->textwithpicto($langs->trans("MAIN_MAIL_SMTPS_PW"), $langs->trans("WithGMailYouCanCreateADedicatedPassword"));
 		print '</td><td>';
 		// SuperAdministrator access only
@@ -430,6 +499,24 @@ if ($action == 'edit') {
 		print '</td></tr>';
 	}
 
+	// OAUTH service provider
+	if (!empty($conf->use_javascript_ajax) || (isset($conf->global->MAIN_MAIL_SENDMODE) && in_array($conf->global->MAIN_MAIL_SENDMODE, array('smtps', 'swiftmailer')))) {
+		print '<tr class="oddeven smtp_oauth_service"><td>'.$langs->trans("MAIN_MAIL_SMTPS_OAUTH_SERVICE").'</td><td>';
+
+		// SuperAdministrator access only
+		if ((empty($conf->global->MAIN_MODULE_MULTICOMPANY)) || ($user->admin && !$user->entity)) {
+			print $form->selectarray('MAIN_MAIL_SMTPS_OAUTH_SERVICE', $oauthservices, $conf->global->MAIN_MAIL_SMTPS_OAUTH_SERVICE);
+		} else {
+			$text = $oauthservices[$conf->global->MAIN_MAIL_SMTPS_OAUTH_SERVICE];
+			if (empty($text)) {
+				$text = $langs->trans("Undefined");
+			}
+			$htmltext = $langs->trans("ContactSuperAdminForChange");
+			print $form->textwithpicto($text, $htmltext, 1, 'superadmin');
+			print '<input type="hidden" name="MAIN_MAIL_SMTPS_OAUTH_SERVICE" value="'.$conf->global->MAIN_MAIL_SMTPS_OAUTH_SERVICE.'">';
+		}
+		print '</td></tr>';
+	}
 	// TLS
 	print '<tr class="oddeven"><td>'.$langs->trans("MAIN_MAIL_EMAIL_TLS").'</td><td>';
 	if (!empty($conf->use_javascript_ajax) || (isset($conf->global->MAIN_MAIL_SENDMODE) && in_array($conf->global->MAIN_MAIL_SENDMODE, array('smtps', 'swiftmailer')))) {
@@ -595,29 +682,46 @@ if ($action == 'edit') {
 		print '</td></tr>';
 
 		// Host server
-		if ($linuxlike && (isset($conf->global->MAIN_MAIL_SENDMODE) && $conf->global->MAIN_MAIL_SENDMODE == 'mail')) {
+		if ($linuxlike && (getDolGlobalString('MAIN_MAIL_SENDMODE') == 'mail')) {
 			print '<tr class="oddeven"><td>'.$langs->trans("MAIN_MAIL_SMTP_SERVER_NotAvailableOnLinuxLike").'</td><td><span class="opacitymedium">'.$langs->trans("SeeLocalSendMailSetup").'</span></td></tr>';
 		} else {
 			print '<tr class="oddeven"><td>'.$langs->trans("MAIN_MAIL_SMTP_SERVER", ini_get('SMTP') ?ini_get('SMTP') : $langs->transnoentities("Undefined")).'</td><td>'.(!empty($conf->global->MAIN_MAIL_SMTP_SERVER) ? $conf->global->MAIN_MAIL_SMTP_SERVER : '').'</td></tr>';
 		}
 
+
 		// Port
-		if ($linuxlike && (isset($conf->global->MAIN_MAIL_SENDMODE) && $conf->global->MAIN_MAIL_SENDMODE == 'mail')) {
+		if ($linuxlike && (getDolGlobalString('MAIN_MAIL_SENDMODE') == 'mail')) {
 			print '<tr class="oddeven"><td>'.$langs->trans("MAIN_MAIL_SMTP_PORT_NotAvailableOnLinuxLike").'</td><td><span class="opacitymedium">'.$langs->trans("SeeLocalSendMailSetup").'</span></td></tr>';
 		} else {
 			print '<tr class="oddeven"><td>'.$langs->trans("MAIN_MAIL_SMTP_PORT", ini_get('smtp_port') ?ini_get('smtp_port') : $langs->transnoentities("Undefined")).'</td><td>'.(!empty($conf->global->MAIN_MAIL_SMTP_PORT) ? $conf->global->MAIN_MAIL_SMTP_PORT : '').'</td></tr>';
 		}
 
 		// SMTPS ID
-		if (isset($conf->global->MAIN_MAIL_SENDMODE) && in_array($conf->global->MAIN_MAIL_SENDMODE, array('smtps', 'swiftmailer'))) {
+		if (in_array(getDolGlobalString('MAIN_MAIL_SENDMODE'), array('smtps', 'swiftmailer'))) {
 			print '<tr class="oddeven"><td>'.$langs->trans("MAIN_MAIL_SMTPS_ID").'</td><td>'.$conf->global->MAIN_MAIL_SMTPS_ID.'</td></tr>';
 		}
 
+		// AUTH method
+		if (in_array(getDolGlobalString('MAIN_MAIL_SENDMODE'), array('smtps', 'swiftmailer'))) {
+			$authtype = getDolGlobalString('MAIN_MAIL_SMTPS_AUTH_TYPE', 'LOGIN');
+			$text = ($authtype === "LOGIN") ? $langs->trans("UsePassword") : ($authtype === "XOAUTH2" ?  $langs->trans("UseOauth") : '') ;
+			print '<tr class="oddeven"><td>'.$langs->trans("MAIN_MAIL_SMTPS_AUTH_TYPE").'</td><td>'.$text.'</td></tr>';
+		}
+
 		// SMTPS PW
-		if (isset($conf->global->MAIN_MAIL_SENDMODE) && in_array($conf->global->MAIN_MAIL_SENDMODE, array('smtps', 'swiftmailer'))) {
+		if (in_array(getDolGlobalString('MAIN_MAIL_SENDMODE'), array('smtps', 'swiftmailer')) && getDolGlobalString('MAIN_MAIL_SMTPS_AUTH_TYPE') != "XOAUTH2") {
 			print '<tr class="oddeven"><td>'.$langs->trans("MAIN_MAIL_SMTPS_PW").'</td><td>'.preg_replace('/./', '*', $conf->global->MAIN_MAIL_SMTPS_PW).'</td></tr>';
 		}
 
+		// SMTPS oauth service
+		if (in_array(getDolGlobalString('MAIN_MAIL_SENDMODE'), array('smtps', 'swiftmailer')) && getDolGlobalString('MAIN_MAIL_SMTPS_AUTH_TYPE') === "XOAUTH2") {
+			$text = $oauthservices[$conf->global->MAIN_MAIL_SMTPS_OAUTH_SERVICE];
+			if (empty($text)) {
+				$text = $langs->trans("Undefined").img_warning();
+			}
+			print '<tr class="oddeven"><td>'.$langs->trans("MAIN_MAIL_SMTPS_OAUTH_SERVICE").'</td><td>'.$text.'</td></tr>';
+		}
+
 		// TLS
 		print '<tr class="oddeven"><td>'.$langs->trans("MAIN_MAIL_EMAIL_TLS").'</td><td>';
 		if (isset($conf->global->MAIN_MAIL_SENDMODE) && in_array($conf->global->MAIN_MAIL_SENDMODE, array('smtps', 'swiftmailer'))) {

+ 1 - 0
htdocs/admin/mails_senderprofile_list.php

@@ -557,6 +557,7 @@ if (!empty($extrafields->attributes[$object->table_element]['computed']) && is_a
 // --------------------------------------------------------------------
 $i = 0;
 $totalarray = array();
+$totalarray['nbfield'] = 0;
 while ($i < ($limit ? min($num, $limit) : $num)) {
 	$obj = $db->fetch_object($resql);
 	if (empty($obj)) {

+ 8 - 3
htdocs/admin/modules.php

@@ -43,10 +43,15 @@ require_once DOL_DOCUMENT_ROOT.'/admin/dolistore/class/dolistore.class.php';
 // Load translation files required by the page
 $langs->loadLangs(array("errors", "admin", "modulebuilder"));
 
-$mode = GETPOSTISSET('mode') ? GETPOST('mode', 'alpha') : (empty($conf->global->MAIN_MODULE_SETUP_ON_LIST_BY_DEFAULT) ? 'commonkanban' : 'common');
-if (empty($mode)) {
-	$mode = 'common';
+// if we set another view list mode, we keep it (till we change one more time)
+if (GETPOSTISSET('mode')) {
+	$mode = GETPOST('mode', 'alpha');
+	if ($mode =='common' || $mode =='commonkanban')
+		dolibarr_set_const($db, "MAIN_MODULE_SETUP_ON_LIST_BY_DEFAULT", $mode, 'chaine', 0, '', $conf->entity);
+} else {
+	$mode = (empty($conf->global->MAIN_MODULE_SETUP_ON_LIST_BY_DEFAULT) ? 'commonkanban' : $conf->global->MAIN_MODULE_SETUP_ON_LIST_BY_DEFAULT);
 }
+
 $action = GETPOST('action', 'aZ09');
 $value = GETPOST('value', 'alpha');
 $page_y = GETPOST('page_y', 'int');

+ 53 - 8
htdocs/admin/oauth.php

@@ -62,7 +62,7 @@ if ($action == 'add') {		// $provider is OAUTH_XXX
 			setEventMessages($langs->trans("AOAuthEntryForThisProviderAndLabelAlreadyHasAKey"), null, 'errors');
 			$error++;
 		} else {
-			dolibarr_set_const($db, $constname, 'ToComplete', 'chaine', 0, '', $conf->entity);
+			dolibarr_set_const($db, $constname, $langs->trans('ToComplete'), 'chaine', 0, '', $conf->entity);
 			setEventMessages($langs->trans("OAuthProviderAdded"), null);
 		}
 	}
@@ -78,6 +78,16 @@ if ($action == 'update') {
 			if (!dolibarr_set_const($db, $constvalue.'_SECRET', GETPOST($constvalue.'_ID') ? GETPOST($constvalue.'_SECRET') : '', 'chaine', 0, '', $conf->entity)) {
 				$error++;
 			}
+			if (GETPOSTISSET($constvalue.'_URLAUTHORIZE')) {
+				if (!dolibarr_set_const($db, $constvalue.'_URLAUTHORIZE', GETPOST($constvalue.'_URLAUTHORIZE'), 'chaine', 0, '', $conf->entity)) {
+					$error++;
+				}
+			}
+			if (GETPOSTISSET($constvalue.'_SCOPE')) {
+				if (!dolibarr_set_const($db, $constvalue.'_SCOPE', GETPOST($constvalue.'_SCOPE'), 'chaine', 0, '', $conf->entity)) {
+					$error++;
+				}
+			}
 		}
 	}
 
@@ -147,11 +157,17 @@ print '<table class="noborder centpercent">';
 
 $i = 0;
 
-//var_dump($list);
+// Define $listinsetup
 foreach ($conf->global as $key => $val) {
 	if (!empty($val) && preg_match('/^OAUTH_.*_ID$/', $key)) {
 		$provider = preg_replace('/_ID$/', '', $key);
-		$listinsetup[] = array($provider.'_NAME', $provider.'_ID', $provider.'_SECRET', 'OAUTH Provider '.str_replace('OAUTH_', '', $provider));
+		$listinsetup[] = array(
+			$provider.'_NAME',
+			$provider.'_ID',
+			$provider.'_SECRET',
+			$provider.'_URLAUTHORIZE',	// For custom oauth links
+			$provider.'_SCOPE'			// For custom oauth links
+		);
 	}
 }
 
@@ -178,12 +194,16 @@ foreach ($listinsetup as $key) {
 
 	$i++;
 
-	print '<tr class="liste_titre'.($i > 1 ? ' liste_titre_add' : '').'">';
 	// Api Name
 	$label = $langs->trans($keyforsupportedoauth2array);
+	print '<tr class="liste_titre'.($i > 1 ? ' liste_titre_add' : '').'">';
 	print '<td>';
 	print img_picto('', $supportedoauth2array[$keyforsupportedoauth2array]['picto'], 'class="pictofixedwidth"');
-	print $label;
+	if ($label == $keyforsupportedoauth2array) {
+		print $supportedoauth2array[$keyforsupportedoauth2array]['name'];
+	} else {
+		print $label;
+	}
 	if ($keyforprovider) {
 		print ' (<b>'.$keyforprovider.'</b>)';
 	} else {
@@ -201,8 +221,15 @@ foreach ($listinsetup as $key) {
 		$redirect_uri = $urlwithroot.'/core/modules/oauth/'.$supportedoauth2array[$keyforsupportedoauth2array]['callbackfile'].'_oauthcallback.php';
 		print '<tr class="oddeven value">';
 		print '<td>'.$langs->trans("UseTheFollowingUrlAsRedirectURI").'</td>';
-		print '<td><input style="width: 80%" type"text" name="uri'.$keyforsupportedoauth2array.'" value="'.$redirect_uri.'">';
+		print '<td><input style="width: 80%" type"text" name="uri'.$keyforsupportedoauth2array.'" value="'.$redirect_uri.'" disabled>';
 		print '</td></tr>';
+
+		if ($keyforsupportedoauth2array == 'OAUTH_OTHER_NAME') {
+			print '<tr class="oddeven value">';
+			print '<td>'.$langs->trans("URLOfServiceForAuthorization").'</td>';
+			print '<td><input style="width: 80%" type"text" name="'.$key[3].'" value="'.getDolGlobalString($key[3]).'" >';
+			print '</td></tr>';
+		}
 	} else {
 		print '<tr class="oddeven value">';
 		print '<td>'.$langs->trans("UseTheFollowingUrlAsRedirectURI").'</td>';
@@ -213,14 +240,32 @@ foreach ($listinsetup as $key) {
 	// Api Id
 	print '<tr class="oddeven value">';
 	print '<td><label for="'.$key[1].'">'.$langs->trans("OAUTH_ID").'</label></td>';
-	print '<td><input type="text" size="100" id="'.$key[1].'" name="'.$key[1].'" value="'.$conf->global->{$key[1]}.'">';
+	print '<td><input type="text" size="100" id="'.$key[1].'" name="'.$key[1].'" value="'.getDolGlobalString($key[1]).'">';
 	print '</td></tr>';
 
 	// Api Secret
 	print '<tr class="oddeven value">';
 	print '<td><label for="'.$key[2].'">'.$langs->trans("OAUTH_SECRET").'</label></td>';
-	print '<td><input type="password" size="100" id="'.$key[2].'" name="'.$key[2].'" value="'.$conf->global->{$key[2]}.'">';
+	print '<td><input type="password" size="100" id="'.$key[2].'" name="'.$key[2].'" value="'.getDolGlobalString($key[2]).'">';
 	print '</td></tr>';
+
+	// TODO Move this into token generation
+	if ($supported) {
+		if ($keyforsupportedoauth2array == 'OAUTH_OTHER_NAME') {
+			print '<tr class="oddeven value">';
+			print '<td>'.$langs->trans("Scopes").'</td>';
+			print '<td>';
+			print '<input style="width: 80%" type"text" name="'.$key[4].'" value="'.getDolGlobalString($key[4]).'" >';
+			print '</td></tr>';
+		} else {
+			print '<tr class="oddeven value">';
+			print '<td>'.$langs->trans("Scopes").'</td>';
+			print '<td>';
+			//print '<input style="width: 80%" type"text" name="'.$key[4].'" value="'.getDolGlobalString($key[4]).'" >';
+			print $supportedoauth2array[$keyforsupportedoauth2array]['defaultscope'];
+			print '</td></tr>';
+		}
+	}
 }
 
 print '</table>'."\n";

+ 40 - 31
htdocs/admin/oauthlogintokens.php

@@ -138,11 +138,17 @@ if (GETPOST('error')) {
 if ($mode == 'setup' && $user->admin) {
 	print '<span class="opacitymedium">'.$langs->trans("OAuthSetupForLogin")."</span><br><br>\n";
 
-	//var_dump($list);
+	// Define $listinsetup
 	foreach ($conf->global as $key => $val) {
 		if (!empty($val) && preg_match('/^OAUTH_.*_ID$/', $key)) {
 			$provider = preg_replace('/_ID$/', '', $key);
-			$listinsetup[] = array($provider.'_NAME', $provider.'_ID', $provider.'_SECRET', 'OAUTH Provider '.str_replace('OAUTH_', '', $provider));
+			$listinsetup[] = array(
+				$provider.'_NAME',
+				$provider.'_ID',
+				$provider.'_SECRET',
+				$provider.'_URLAUTHORIZE',	// For custom oauth links
+				$provider.'_SCOPE'			// For custom oauth links
+			);
 		}
 	}
 
@@ -165,46 +171,39 @@ if ($mode == 'setup' && $user->admin) {
 
 		$OAUTH_SERVICENAME = (empty($supportedoauth2array[$keyforsupportedoauth2array]['name']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['name'].($keyforprovider ? '-'.$keyforprovider : ''));
 
-		// Define $shortscope, $urltorenew, $urltodelete, $urltocheckperms
+		$shortscope = $supportedoauth2array[$keyforsupportedoauth2array]['defaultscope'];
+		if (getDolGlobalString($key[4])) {
+			$shortscope = getDolGlobalString($key[4]);
+		}
+		$state = $shortscope;	// TODO USe a better state
+
+		// Define $urltorenew, $urltodelete, $urltocheckperms
 		// TODO Use array $supportedoauth2array
 		if ($keyforsupportedoauth2array == 'OAUTH_GITHUB_NAME') {
 			// List of keys that will be converted into scopes (from constants 'SCOPE_state_in_uppercase' in file of service).
 			// We pass this param list in to 'state' because we need it before and after the redirect.
-			$shortscope = 'user,public_repo';
 
-			// Note: github does not accept csrf key inside the state parameter (only know values)
-			$urltorenew = $urlwithroot.'/core/modules/oauth/github_oauthcallback.php?shortscope='.$shortscope.'&state='.$shortscope.'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php');
+			// Note: github does not accept csrf key inside the state parameter (only known values)
+			$urltorenew = $urlwithroot.'/core/modules/oauth/github_oauthcallback.php?shortscope='.urlencode($shortscope).'&state='.$shortscope.'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php');
 			$urltodelete = $urlwithroot.'/core/modules/oauth/github_oauthcallback.php?action=delete&token='.newToken().'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php');
 			$urltocheckperms = 'https://github.com/settings/applications/';
 		} elseif ($keyforsupportedoauth2array == 'OAUTH_GOOGLE_NAME') {
 			// List of keys that will be converted into scopes (from constants 'SCOPE_state_in_uppercase' in file of service).
 			// List of scopes for Google are here: https://developers.google.com/identity/protocols/oauth2/scopes
 			// We pass this key list into the param 'state' because we need it before and after the redirect.
-			$shortscope = 'userinfo_email,userinfo_profile';
-			$shortscope .= ',openid,email,profile';	// For openid connect
-			if (!empty($conf->printing->enabled)) {
-				$shortscope .= ',cloud_print';
-			}
-			if (!empty($conf->global->OAUTH_GOOGLE_GSUITE)) {
-				$shortscope .= ',admin_directory_user';
-			}
-			if (!empty($conf->global->OAUTH_GOOGLE_GMAIL)) {
-				$shortscope.=',gmail_full';
-			}
-
-			$urltorenew = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?shortscope='.$shortscope.'&state='.$shortscope.'-'.$oauthstateanticsrf.'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php');
+			$urltorenew = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?shortscope='.urlencode($shortscope).'&state='.urlencode($state).'-'.$oauthstateanticsrf.'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php');
 			$urltodelete = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?action=delete&token='.newToken().'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php');
 			$urltocheckperms = 'https://security.google.com/settings/security/permissions';
 		} elseif ($keyforsupportedoauth2array == 'OAUTH_STRIPE_TEST_NAME') {
-			$shortscope = 'none';
-
-			$urltorenew = $urlwithroot.'/core/modules/oauth/stripetest_oauthcallback.php?backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php');
+			$urltorenew = $urlwithroot.'/core/modules/oauth/stripetest_oauthcallback.php?shortscope='.urlencode($shortscope).'&state='.urlencode($state).'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php');
 			$urltodelete = '';
 			$urltocheckperms = '';
 		} elseif ($keyforsupportedoauth2array == 'OAUTH_STRIPE_LIVE_NAME') {
-			$shortscope = 'none';
-
-			$urltorenew = $urlwithroot.'/core/modules/oauth/stripelive_oauthcallback.php?backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php');
+			$urltorenew = $urlwithroot.'/core/modules/oauth/stripelive_oauthcallback.php?shortscope='.urlencode($shortscope).'&state='.urlencode($state).'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php');
+			$urltodelete = '';
+			$urltocheckperms = '';
+		} elseif ($keyforsupportedoauth2array = 'OAUTH_OTHER_NAME') {
+			$urltorenew = $urlwithroot.'/core/modules/oauth/generic_oauthcallback.php?shortscope='.urlencode($shortscope).'&state='.urlencode($state).'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php');
 			$urltodelete = '';
 			$urltocheckperms = '';
 		} else {
@@ -212,7 +211,8 @@ if ($mode == 'setup' && $user->admin) {
 			$urltodelete = '';
 			$urltocheckperms = '';
 		}
-		$urltorenew .= '&keyforprovider='.$keyforprovider;
+
+		$urltorenew .= '&keyforprovider='.urlencode($keyforprovider);
 
 		// Show value of token
 		$tokenobj = null;
@@ -246,7 +246,7 @@ if ($mode == 'setup' && $user->admin) {
 				} elseif ($endoflife == $tokenobj::EOL_UNKNOWN) {
 					$expiredat = $langs->trans("Unknown");
 				} else {
-					$expiredat = dol_print_date($endoflife, "dayhour");
+					$expiredat = dol_print_date($endoflife, "dayhour", 'tzuserrel');
 				}
 			}
 		}
@@ -260,10 +260,16 @@ if ($mode == 'setup' && $user->admin) {
 		print '<div class="div-table-responsive-no-min">';
 		print '<table class="noborder centpercent">'."\n";
 
+		// Api Name
+		$label = $langs->trans($keyforsupportedoauth2array);
 		print '<tr class="liste_titre">';
 		print '<th class="titlefieldcreate">';
 		print img_picto('', $supportedoauth2array[$keyforsupportedoauth2array]['picto'], 'class="pictofixedwidth"');
-		print $langs->trans($keyforsupportedoauth2array);
+		if ($label == $keyforsupportedoauth2array) {
+			print $supportedoauth2array[$keyforsupportedoauth2array]['name'];
+		} else {
+			print $label;
+		}
 		if ($keyforprovider) {
 			print ' (<b>'.$keyforprovider.'</b>)';
 		} else {
@@ -292,7 +298,8 @@ if ($mode == 'setup' && $user->admin) {
 		print '</td>';
 		print '<td>';
 		if (is_object($tokenobj)) {
-			print $langs->trans("HasAccessToken");
+			// TODO Read in database to get the date of creation of token
+			print $form->textwithpicto(yn(1), $langs->trans("HasAccessToken").' : ');
 		} else {
 			print '<span class="opacitymedium">'.$langs->trans("NoAccessToken").'</span>';
 		}
@@ -305,7 +312,9 @@ if ($mode == 'setup' && $user->admin) {
 		}
 		// Request remote token
 		if ($urltorenew) {
-			print '<a class="button smallpaddingimp" href="'.$urltorenew.'">'.$langs->trans('RequestAccess').'</a><br>';
+			print '<a class="button smallpaddingimp" href="'.$urltorenew.'">'.$langs->trans('GetAccess').'</a>';
+			print $form->textwithpicto('', $langs->trans('RequestAccess'));
+			print '<br>';
 		}
 		// Check remote access
 		if ($urltocheckperms) {
@@ -378,8 +387,8 @@ if ($mode == 'setup' && $user->admin) {
 			}
 		}
 
-
 		print '</form>';
+		print '<br>';
 	}
 }
 

+ 19 - 16
htdocs/barcode/printsheet.php

@@ -203,25 +203,30 @@ if ($action == 'builddoc') {
 			$forceimgscalewidth = (empty($conf->global->BARCODE_FORCEIMGSCALEWIDTH) ? 1 : $conf->global->BARCODE_FORCEIMGSCALEWIDTH);
 			$forceimgscaleheight = (empty($conf->global->BARCODE_FORCEIMGSCALEHEIGHT) ? 1 : $conf->global->BARCODE_FORCEIMGSCALEHEIGHT);
 
-			for ($i = 0; $i < $numberofsticker; $i++) {
-				$arrayofrecords[] = array(
-					'textleft'=>$textleft,
-					'textheader'=>$textheader,
-					'textfooter'=>$textfooter,
-					'textright'=>$textright,
-					'code'=>$code,
-					'encoding'=>$encoding,
-					'is2d'=>$is2d,
-					'photo'=>$barcodeimage	// Photo must be a file that exists with format supported by TCPDF
-				);
+			$MAXSTICKERS = 1000;
+			if ($numberofsticker <= $MAXSTICKERS) {
+				for ($i = 0; $i < $numberofsticker; $i++) {
+					$arrayofrecords[] = array(
+						'textleft'=>$textleft,
+						'textheader'=>$textheader,
+						'textfooter'=>$textfooter,
+						'textright'=>$textright,
+						'code'=>$code,
+						'encoding'=>$encoding,
+						'is2d'=>$is2d,
+						'photo'=>$barcodeimage	// Photo must be a file that exists with format supported by TCPDF
+					);
+				}
+			} else {
+				$mesg = $langs->trans("ErrorQuantityIsLimitedTo", $MAXSTICKERS);
+				$error++;
 			}
 		}
 
 		$i++;
-		$mesg = '';
 
 		// Build and output PDF
-		if ($mode == 'label') {
+		if (!$error && $mode == 'label') {
 			if (!count($arrayofrecords)) {
 				$mesg = $langs->trans("ErrorRecordNotFound");
 			}
@@ -240,7 +245,7 @@ if ($action == 'builddoc') {
 			}
 		}
 
-		if ($result <= 0 || $mesg) {
+		if ($result <= 0 || $mesg || $error) {
 			if (empty($mesg)) {
 				$mesg = 'Error '.$result;
 			}
@@ -272,8 +277,6 @@ print '<br>';
 print '<span class="opacitymedium">'.$langs->trans("PageToGenerateBarCodeSheets", $langs->transnoentitiesnoconv("BuildPageToPrint")).'</span><br>';
 print '<br>';
 
-dol_htmloutput_errors($mesg);
-
 //print img_picto('','puce').' '.$langs->trans("PrintsheetForOneBarCode").'<br>';
 //print '<br>';
 

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

@@ -305,7 +305,8 @@ class Categories extends DolibarrApi
 			Categorie::TYPE_CUSTOMER,
 			Categorie::TYPE_SUPPLIER,
 			Categorie::TYPE_MEMBER,
-			Categorie::TYPE_PROJECT
+			Categorie::TYPE_PROJECT,
+			Categorie::TYPE_KNOWLEDGEMANAGEMENT
 		])) {
 			throw new RestException(401);
 		}
@@ -322,6 +323,8 @@ class Categories extends DolibarrApi
 			throw new RestException(401);
 		} elseif ($type == Categorie::TYPE_PROJECT && !DolibarrApiAccess::$user->rights->projet->lire) {
 			throw new RestException(401);
+		} elseif ($type == Categorie::TYPE_KNOWLEDGEMANAGEMENT && !DolibarrApiAccess::$user->rights->knowledgemanagement->knowledgerecord->read) {
+			throw new RestException(401);
 		}
 
 		$categories = $this->category->getListForItem($id, $type, $sortfield, $sortorder, $limit, $page);

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

@@ -703,13 +703,14 @@ class Categorie extends CommonObject
 			$type = $obj->element;
 		}
 
+		dol_syslog(get_class($this).'::add_type', LOG_DEBUG);
+
 		$this->db->begin();
 
 		$sql = "INSERT INTO ".MAIN_DB_PREFIX."categorie_".(empty($this->MAP_CAT_TABLE[$type]) ? $type : $this->MAP_CAT_TABLE[$type]);
 		$sql .= " (fk_categorie, fk_".(empty($this->MAP_CAT_FK[$type]) ? $type : $this->MAP_CAT_FK[$type]).")";
 		$sql .= " VALUES (".((int) $this->id).", ".((int) $obj->id).")";
 
-		dol_syslog(get_class($this).'::add_type', LOG_DEBUG);
 		if ($this->db->query($sql)) {
 			if (!empty($conf->global->CATEGORIE_RECURSIV_ADD)) {
 				$sql = 'SELECT fk_parent FROM '.MAIN_DB_PREFIX.'categorie';

+ 4 - 1
htdocs/categories/viewcat.php

@@ -1030,7 +1030,7 @@ if ($type == Categorie::TYPE_PROJECT) {
 }
 
 // List of users
-if ($type == Categorie::TYPE_USER) {
+if ($type == Categorie::TYPE_USER && $user->hasRight("user", "user", "read")) {
 	require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
 
 	$users = $object->getObjectsInCateg($type);
@@ -1099,6 +1099,9 @@ if ($type == Categorie::TYPE_USER) {
 
 		print '</form>'."\n";
 	}
+} else {
+	print_barre_liste($langs->trans("Users"), null, $_SERVER["PHP_SELF"], '', '', '', '', '', '', 'user');
+	accessforbidden($langs->trans("NotEnoughPermissions"), 0, 0);
 }
 
 

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

@@ -538,7 +538,7 @@ if (empty($reshook) && $action == 'add') {
 				$error++;
 			}
 			// End date
-			$repeateventlimitdate = dol_mktime('23', '59', '59', GETPOSTISSET("limitmonth", 'int') ? GETPOST("limitmonth", 'int') :  01, GETPOSTISSET("limitday", 'int') ? GETPOST("limitday", 'int') : 01, GETPOSTISSET("limityear", 'int') && GETPOST("limityear", 'int') < 2100 ? GETPOST("limityear", 'int') : 2100, $tzforfullday ? $tzforfullday : 'tzuser');
+			$repeateventlimitdate = dol_mktime('23', '59', '59', GETPOSTISSET("limitmonth") ? GETPOST("limitmonth", 'int') :  01, GETPOSTISSET("limitday", 'int') ? GETPOST("limitday", 'int') : 01, GETPOSTISSET("limityear", 'int') && GETPOST("limityear", 'int') < 2100 ? GETPOST("limityear", 'int') : 2100, $tzforfullday ? $tzforfullday : 'tzuser');
 			// Set date of end of event
 			$deltatime = num_between_day($object->datep, $datep);
 			$datef = dol_time_plus_duree($datef, $deltatime, 'd');
@@ -677,7 +677,7 @@ if (empty($reshook) && $action == 'update') {
 		$object->fetch($id);
 		$object->fetch_optionals();
 		$object->fetch_userassigned();
-		$object->oldcopy = clone $object;
+		$object->oldcopy = dol_clone($object);
 
 		// Clean parameters
 		if ($fulldayevent) {
@@ -927,7 +927,7 @@ if (empty($reshook) && $action == 'confirm_delete' && GETPOST("confirm") == 'yes
 	$object->fetch($id);
 	$object->fetch_optionals();
 	$object->fetch_userassigned();
-	$object->oldcopy = clone $object;
+	$object->oldcopy = dol_clone($object);
 
 	if ($user->rights->agenda->myactions->delete
 		|| $user->rights->agenda->allactions->delete) {

+ 1 - 1
htdocs/comm/action/index.php

@@ -212,7 +212,7 @@ if (GETPOST("viewperuser", 'alpha') || $mode == 'show_peruser') {
  $event->fetch($actionid);
  $event->fetch_optionals();
  $event->fetch_userassigned();
- $event->oldcopy = clone $event;
+ $event->oldcopy = dol_clone($event);
 
  $result = $event->delete();
  }

+ 1 - 1
htdocs/comm/action/pertype.php

@@ -189,7 +189,7 @@ if ($action == 'delete_action' && $user->rights->agenda->delete) {
 	$event->fetch($actionid);
 	$event->fetch_optionals();
 	$event->fetch_userassigned();
-	$event->oldcopy = clone $event;
+	$event->oldcopy = dol_clone($event);
 
 	$result = $event->delete();
 }

+ 1 - 1
htdocs/comm/action/peruser.php

@@ -196,7 +196,7 @@ if ($action == 'delete_action' && $user->rights->agenda->delete) {
 	$event->fetch($actionid);
 	$event->fetch_optionals();
 	$event->fetch_userassigned();
-	$event->oldcopy = clone $event;
+	$event->oldcopy = dol_clone($event);
 
 	$result = $event->delete();
 }

+ 1 - 2
htdocs/comm/multiprix.php

@@ -75,7 +75,7 @@ if ($_socid > 0) {
 	// We load data of thirdparty
 	$objsoc = new Societe($db);
 	$objsoc->id = $_socid;
-	$objsoc->fetch($_socid, $to);
+	$objsoc->fetch($_socid);
 
 
 	$head = societe_prepare_head($objsoc);
@@ -141,7 +141,6 @@ if ($_socid > 0) {
 	$resql = $db->query($sql);
 	if ($resql) {
 		print '<table class="noborder centpercent">';
-		$tag = !$tag;
 		print '<tr class="liste_titre">';
 		print '<td>'.$langs->trans("Date").'</td>';
 		print '<td>'.$langs->trans("PriceLevel").'</td>';

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

@@ -112,9 +112,9 @@ if ($id > 0 || !empty($ref)) {
 // Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
 $hookmanager->initHooks(array('propalcard', 'globalcard'));
 
-$usercanread = $user->rights->propal->lire;
-$usercancreate = $user->rights->propal->creer;
-$usercandelete = $user->rights->propal->supprimer;
+$usercanread = $user->hasRight("propal", "lire");
+$usercancreate = $user->hasRight("propal", "creer");
+$usercandelete = $user->hasRight("propal", "supprimer");
 
 $usercanclose = ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && $usercancreate) || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->propal_advance->close)));
 $usercanvalidate = ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && $usercancreate) || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->propal_advance->validate)));
@@ -1851,7 +1851,7 @@ if ($action == 'create') {
 			// Calcul contrat->price (HT), contrat->total (TTC), contrat->tva
 			$objectsrc->remise_absolue = $remise_absolue;	// deprecated
 			$objectsrc->remise_percent = $remise_percent;
-			$objectsrc->update_price(1, - 1, 1);
+			$objectsrc->update_price(1, 'auto', 1);
 		}
 
 		print "\n<!-- ".$classname." info -->";

+ 3 - 3
htdocs/commande/card.php

@@ -108,9 +108,9 @@ $extrafields->fetch_name_optionals_label($object->table_element);
 // Load object
 include DOL_DOCUMENT_ROOT.'/core/actions_fetchobject.inc.php'; // Must be include, not include_once
 
-$usercanread = $user->hasRight('commande', 'lire');
-$usercancreate = $user->hasRight('commande', 'creer');
-$usercandelete = $user->hasRight('commande', 'supprimer');
+$usercanread = $user->hasRight("commande", "lire");
+$usercancreate = $user->hasRight("commande", "creer");
+$usercandelete = $user->hasRight("commande", "supprimer");
 // Advanced permissions
 
 $usercanclose = ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($usercancreate)) || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->hasRight('commande', 'order_advance', 'close'))));

+ 5 - 1
htdocs/compta/bank/bankentries_list.php

@@ -235,8 +235,12 @@ if (empty($reshook)) {
 	include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
 }
 
+$rowids = GETPOST('rowid', 'array');
+
 // Conciliation
-if ((GETPOST('confirm_savestatement', 'alpha') || GETPOST('confirm_reconcile', 'alpha')) && !empty($user->rights->banque->consolidate)
+if ((GETPOST('confirm_savestatement', 'alpha') || GETPOST('confirm_reconcile', 'alpha'))
+	&& (GETPOST("num_releve", "alpha") || !empty($rowids))
+	&& !empty($user->rights->banque->consolidate)
 	&& (!GETPOSTISSET('pageplusone') || (GETPOST('pageplusone') == GETPOST('pageplusoneold')))) {
 	$error = 0;
 

+ 4 - 1
htdocs/compta/bank/card.php

@@ -846,6 +846,9 @@ if ($action == 'create') {
 		$object = new Account($db);
 		$object->fetch(GETPOST('id', 'int'));
 
+		$title = $object->ref." - ".$langs->trans("Card");
+		llxHeader("", $title, $help_url);
+
 		print load_fiche_titre($langs->trans("EditFinancialAccount"), '', 'bank_account');
 
 		if ($conf->use_javascript_ajax) {
@@ -1014,7 +1017,7 @@ if ($action == 'create') {
 			$tdextra = ' class="fieldrequired titlefieldcreate"';
 		}
 
-		print '<tr class="liste_titre_add"><td'.$tdextra.'>'.$langs->trans("AccountancyCode").'</td>';
+		print '<tr><td'.$tdextra.'>'.$langs->trans("AccountancyCode").'</td>';
 		print '<td>';
 		if (!empty($conf->accounting->enabled)) {
 			print $formaccounting->select_account($object->account_number, 'account_number', 1, '', 1, 1);

+ 6 - 6
htdocs/compta/facture/card.php

@@ -128,11 +128,11 @@ if ($id > 0 || !empty($ref)) {
 // Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
 $hookmanager->initHooks(array('invoicecard', 'globalcard'));
 
-$usercanread = $user->rights->facture->lire;
-$usercancreate = $user->rights->facture->creer;
-$usercanissuepayment = $user->rights->facture->paiement;
-$usercandelete = $user->rights->facture->supprimer;
-$usercancreatecontract = $user->rights->contrat->creer;
+$usercanread = $user->hasRight("facture", "lire");
+$usercancreate = $user->hasRight("facture", "creer");
+$usercanissuepayment = $user->hasRight("facture", "paiement");
+$usercandelete = $user->hasRight("facture", "supprimer");
+$usercancreatecontract = $user->hasRight("contrat", "creer");
 $usercanvalidate = ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && $usercancreate) || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->facture->invoice_advance->validate)));
 $usercansend = (empty($conf->global->MAIN_USE_ADVANCED_PERMS) || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->facture->invoice_advance->send)));
 $usercanreopen = ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && $usercancreate) || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->facture->invoice_advance->reopen)));
@@ -3785,7 +3785,7 @@ if ($action == 'create') {
 			// Calcul contrat->price (HT), contrat->total (TTC), contrat->tva
 			$objectsrc->remise_absolue = $remise_absolue;
 			$objectsrc->remise_percent = $remise_percent;
-			$objectsrc->update_price(1, - 1, 1);
+			$objectsrc->update_price(1, 'auto', 1);
 		}
 
 		print "\n<!-- Show ref of origin ".$classname." -->\n";

+ 31 - 7
htdocs/compta/paiement/cheque/card.php

@@ -59,8 +59,15 @@ $limit = GETPOST('limit', 'int') ?GETPOST('limit', 'int') : $conf->liste_limit;
 $offset = $limit * $page;
 
 $upload_dir = $conf->bank->multidir_output[$object->entity ? $object->entity : $conf->entity]."/checkdeposits";
-
-$filterdate = dol_mktime(0, 0, 0, GETPOST('fdmonth'), GETPOST('fdday'), GETPOST('fdyear'));
+// filter by dates from / to
+$search_date_start_day = GETPOST('search_date_start_day', 'int');
+$search_date_start_month = GETPOST('search_date_start_month', 'int');
+$search_date_start_year = GETPOST('search_date_start_year', 'int');
+$search_date_end_day = GETPOST('search_date_end_day', 'int');
+$search_date_end_month = GETPOST('search_date_end_month', 'int');
+$search_date_end_year = GETPOST('search_date_end_year', 'int');
+$search_date_start = dol_mktime(0, 0, 0, $search_date_start_month, $search_date_start_day, $search_date_start_year);
+$search_date_end = dol_mktime(23, 59, 59, $search_date_end_month, $search_date_end_day, $search_date_end_year);
 $filteraccountid = GETPOST('accountid', 'int');
 
 // Security check
@@ -265,7 +272,15 @@ if ($action == 'builddoc' && $user->rights->banque->cheque) {
  */
 
 if (GETPOST('removefilter')) {
-	$filterdate = '';
+	// filter by dates from / to
+	$search_date_start_day = '';
+	$search_date_start_month = '';
+	$search_date_start_year = '';
+	$search_date_end_day = '';
+	$search_date_end_month = '';
+	$search_date_end_year = '';
+	$search_date_start = '';
+	$search_date_end = '';
 	$filteraccountid = 0;
 }
 
@@ -352,7 +367,13 @@ if ($action == 'new') {
 	//print '<tr><td width="30%">'.$langs->trans('Date').'</td><td width="70%">'.dol_print_date($now,'day').'</td></tr>';
 	// Filter
 	print '<tr><td class="titlefieldcreate">'.$langs->trans("DateChequeReceived").'</td><td>';
-	print $form->selectDate($filterdate, 'fd', 0, 0, 1, '', 1, 1);
+	// filter by dates from / to
+	print '<div class="nowrap">';
+	print $form->selectDate($search_date_start, 'search_date_start_', 0, 0, 1, '', 1, 1, 0, '', '', '', '', 1, '', $langs->trans('From'));
+	print '</div>';
+	print '<div class="nowrap">';
+	print $form->selectDate($search_date_end, 'search_date_end_', 0, 0, 1, '', 1, 1, 0, '', '', '', '', 1, '', $langs->trans('to'));
+	print '</div>';
 	print '</td></tr>';
 	print '<tr><td>'.$langs->trans("BankAccount").'</td><td>';
 	$form->select_comptes($filteraccountid, 'accountid', 0, 'courant <> 2', 1);
@@ -363,7 +384,7 @@ if ($action == 'new') {
 
 	print '<div class="center">';
 	print '<input type="submit" class="button" name="filter" value="'.dol_escape_htmltag($langs->trans("ToFilter")).'">';
-	if ($filterdate || $filteraccountid > 0) {
+	if ($search_date_start || $search_date_end || $filteraccountid > 0) {
 		print ' &nbsp; ';
 		print '<input type="submit" class="button" name="removefilter" value="'.dol_escape_htmltag($langs->trans("RemoveFilter")).'">';
 	}
@@ -382,8 +403,11 @@ if ($action == 'new') {
 	$sql .= " AND ba.entity IN (".getEntity('bank_account').")";
 	$sql .= " AND b.fk_bordereau = 0";
 	$sql .= " AND b.amount > 0";
-	if ($filterdate) {
-		$sql .= " AND b.dateo = '".$db->idate($filterdate)."'";
+	if ($search_date_start) {
+		$sql .= " AND b.dateo >= '".$db->idate($search_date_start)."'";
+	}
+	if ($search_date_end) {
+		$sql .= " AND b.dateo <= '".$db->idate($search_date_end)."'";
 	}
 	if ($filteraccountid > 0) {
 		$sql .= " AND ba.rowid = ".((int) $filteraccountid);

+ 1 - 6
htdocs/compta/paiement_charge.php

@@ -169,13 +169,8 @@ if ($action == 'create') {
 	}
 
 	print load_fiche_titre($langs->trans("DoPayment"));
-	print "<br>\n";
 
-	if ($mesg) {
-		print "<div class=\"error\">$mesg</div>";
-	}
-
-	print '<form name="add_payment" action="'.$_SERVER['PHP_SELF'].'" method="post">';
+	print '<form name="add_payment" action="'.$_SERVER['PHP_SELF'].'" method="POST">';
 	print '<input type="hidden" name="token" value="'.newToken().'">';
 	print '<input type="hidden" name="id" value="'.$chid.'">';
 	print '<input type="hidden" name="chid" value="'.$chid.'">';

+ 1 - 1
htdocs/conf/conf.php.example

@@ -185,7 +185,7 @@ $dolibarr_main_instance_unique_id='84b5bc91f83b56e458db71e0adac2b62';
 // values using a ",". In this case, Dolibarr will check login/pass for each value in
 // order defined into value. However, note that this can't work with all values.
 // Examples:
-// $dolibarr_main_authentication='dolibarr';		// Use the password defined into application on user record.
+// $dolibarr_main_authentication='dolibarr';		// Use the password defined into application on user file (default).
 // $dolibarr_main_authentication='http';			// Use the HTTP Basic authentication
 // $dolibarr_main_authentication='ldap';			// Check the password into a LDAP server
 // $dolibarr_main_authentication='ldap,dolibarr';	// You can set several mode using a comma as a separator.

+ 1 - 1
htdocs/contact/perso.php

@@ -60,7 +60,7 @@ if ($action == 'update' && !GETPOST("cancel") && $user->rights->societe->contact
 
 	$result = $object->update_perso($id, $user);
 	if ($result > 0) {
-		$object->oldcopy = clone $object;
+		$object->oldcopy = dol_clone($object);
 
 		// Logo/Photo save
 		$dir = $conf->societe->dir_output.'/contact/'.get_exdir($object->id, 0, 0, 1, $object, 'contact').'/photos';

+ 1 - 2
htdocs/contrat/class/contrat.class.php

@@ -3135,8 +3135,7 @@ class ContratLigne extends CommonObjectLine
 
 		$this->db->begin();
 
-		$this->oldcopy = new ContratLigne($this->db);
-		$this->oldcopy->fetch($this->id);
+		$this->oldcopy = dol_clone($this);
 
 		// Update request
 		$sql = "UPDATE ".MAIN_DB_PREFIX."contratdet SET";

+ 2 - 2
htdocs/contrat/list.php

@@ -872,8 +872,8 @@ while ($i < min($num, $limit)) {
 	}
 	// Country
 	if (!empty($arrayfields['country.code_iso']['checked'])) {
-		print '<td class="center">';
-		print $socstatic->country;
+		print '<td class="center tdoverflowmax100" title="'.dol_escape_htmltag($socstatic->country).'">';
+		print dol_escape_htmltag($socstatic->country);
 		print '</td>';
 		if (!$i) {
 			$totalarray['nbfield']++;

+ 8 - 3
htdocs/core/actions_sendmails.inc.php

@@ -361,7 +361,7 @@ if (($action == 'send' || $action == 'relance') && !GETPOST('addfile') && !GETPO
 			}
 			$mailfile = new CMailFile($subject, $sendto, $from, $message, $filepath, $mimetype, $filename, $sendtocc, $sendtobcc, $deliveryreceipt, -1, '', '', $trackid, '', $sendcontext);
 
-			if ($mailfile->error) {
+			if (!empty($mailfile->error) || !empty($mailfile->errors)) {
 				setEventMessages($mailfile->error, $mailfile->errors, 'errors');
 				$action = 'presend';
 			} else {
@@ -427,9 +427,14 @@ if (($action == 'send' || $action == 'relance') && !GETPOST('addfile') && !GETPO
 				} else {
 					$langs->load("other");
 					$mesg = '<div class="error">';
-					if ($mailfile->error) {
+					if (!empty($mailfile->error) || !empty($mailfile->errors)) {
 						$mesg .= $langs->transnoentities('ErrorFailedToSendMail', dol_escape_htmltag($from), dol_escape_htmltag($sendto));
-						$mesg .= '<br>'.$mailfile->error;
+						if (!empty($mailfile->error)) {
+							$mesg .= '<br>'.$mailfile->error;
+						}
+						if (!empty($mailfile->errors) && is_array($mailfile->errors)) {
+							$mesg .= '<br>'.implode('<br>', $mailfile->errors);
+						}
 					} else {
 						$mesg .= $langs->transnoentities('ErrorFailedToSendMail', dol_escape_htmltag($from), dol_escape_htmltag($sendto));
 						if (!empty($conf->global->MAIN_DISABLE_ALL_MAILS)) {

+ 122 - 12
htdocs/core/class/CMailFile.class.php

@@ -30,6 +30,8 @@
  *      \brief      File of class to send emails (with attachments or not)
  */
 
+use OAuth\Common\Storage\DoliStorage;
+use OAuth\Common\Consumer\Credentials;
 /**
  *	Class to send emails (with attachments or not)
  *  Usage: $mailfile = new CMailFile($subject,$sendto,$replyto,$message,$filepath,$mimetype,$filename,$cc,$ccc,$deliveryreceipt,$msgishtml,$errors_to,$css,$trackid,$moreinheader,$sendcontext,$replyto);
@@ -71,9 +73,29 @@ class CMailFile
 	 */
 	public $error = '';
 
+	/**
+	 * @var string[] Array of Error code (or message)
+	 */
+	public $errors = array();
+
 	public $smtps; // Contains SMTPs object (if this method is used)
 	public $phpmailer; // Contains PHPMailer object (if this method is used)
 
+	/**
+	 * @var Swift_SmtpTransport
+	 */
+	public $transport;
+
+	/**
+	 * @var Swift_Mailer
+	 */
+	public $mailer;
+
+	/**
+	 * @var Swift_Plugins_Loggers_ArrayLogger
+	 */
+	public $logger;
+
 	/**
 	 * @var string CSS
 	 */
@@ -470,12 +492,12 @@ class CMailFile
 						$emailMatchs = preg_match_all($regexp, $from, $adressEmailFrom);
 						$adressEmailFrom = reset($adressEmailFrom);
 						if ($emailMatchs !== false && filter_var($conf->global->MAIN_MAIL_SMTPS_ID, FILTER_VALIDATE_EMAIL) && $conf->global->MAIN_MAIL_SMTPS_ID !== $adressEmailFrom) {
-							$result = $this->message->setFrom($conf->global->MAIN_MAIL_SMTPS_ID);
+							$this->message->setFrom($conf->global->MAIN_MAIL_SMTPS_ID);
 						} else {
-							$result = $this->message->setFrom($this->getArrayAddress($this->addr_from));
+							$this->message->setFrom($this->getArrayAddress($this->addr_from));
 						}
 					} else {
-						$result = $this->message->setFrom($this->getArrayAddress($this->addr_from));
+						$this->message->setFrom($this->getArrayAddress($this->addr_from));
 					}
 				} catch (Exception $e) {
 					$this->errors[] = $e->getMessage();
@@ -485,7 +507,7 @@ class CMailFile
 			// Set the To addresses with an associative array
 			if (!empty($this->addr_to)) {
 				try {
-					$result = $this->message->setTo($this->getArrayAddress($this->addr_to));
+					$this->message->setTo($this->getArrayAddress($this->addr_to));
 				} catch (Exception $e) {
 					$this->errors[] = $e->getMessage();
 				}
@@ -493,14 +515,14 @@ class CMailFile
 
 			if (!empty($this->reply_to)) {
 				try {
-					$result = $this->message->SetReplyTo($this->getArrayAddress($this->reply_to));
+					$this->message->SetReplyTo($this->getArrayAddress($this->reply_to));
 				} catch (Exception $e) {
 					$this->errors[] = $e->getMessage();
 				}
 			}
 
 			try {
-				$result = $this->message->setCharSet($conf->file->character_set_client);
+				$this->message->setCharSet($conf->file->character_set_client);
 			} catch (Exception $e) {
 				$this->errors[] = $e->getMessage();
 			}
@@ -562,7 +584,11 @@ class CMailFile
 			}
 			//if (! empty($this->errors_to)) $this->message->setErrorsTo($this->getArrayAddress($this->errors_to));
 			if (isset($this->deliveryreceipt) && $this->deliveryreceipt == 1) {
-				$this->message->setReadReceiptTo($this->getArrayAddress($this->addr_from));
+				try {
+					$this->message->setReadReceiptTo($this->getArrayAddress($this->addr_from));
+				} catch (Exception $e) {
+					$this->errors[] = $e->getMessage();
+				}
 			}
 		} else {
 			// Send mail method not correctly defined
@@ -665,6 +691,8 @@ class CMailFile
 			}
 
 			$keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER';
+			$keyforsmtpauthtype = "MAIN_MAIL_SMTPS_AUTH_TYPE";
+			$keyforsmtpoauthservice = "MAIN_MAIL_SMTPS_OAUTH_SERVICE";
 			$keyforsmtpport  = 'MAIN_MAIL_SMTP_PORT';
 			$keyforsmtpid    = 'MAIN_MAIL_SMTPS_ID';
 			$keyforsmtppw    = 'MAIN_MAIL_SMTPS_PW';
@@ -840,6 +868,36 @@ class CMailFile
 					$this->smtps->setPW($loginpass);
 				}
 
+				if (getDolGlobalString($keyforsmtpauthtype) === "XOAUTH2") {
+					require_once DOL_DOCUMENT_ROOT.'/core/lib/oauth.lib.php'; // define $supportedoauth2array
+					$keyforsupportedoauth2array = $conf->global->$keyforsmtpoauthservice;
+					if (preg_match('/^.*-/', $keyforsupportedoauth2array)) {
+						$keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array);
+					} else {
+						$keyforprovider = '';
+					}
+					$keyforsupportedoauth2array = preg_replace('/-.*$/', '', $keyforsupportedoauth2array);
+					$keyforsupportedoauth2array = 'OAUTH_'.$keyforsupportedoauth2array.'_NAME';
+
+					$OAUTH_SERVICENAME = (empty($supportedoauth2array[$keyforsupportedoauth2array]['name']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['name'].($keyforprovider ? '-'.$keyforprovider : ''));
+
+					require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php';
+
+					$storage = new DoliStorage($db, $conf);
+					try {
+						$tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME);
+						if (is_object($tokenobj)) {
+							$this->smtps->setToken($tokenobj->getAccessToken());
+						} else {
+							$this->error = "Token not found";
+						}
+					} catch (Exception $e) {
+						// Return an error if token not found
+						$this->error = $e->getMessage();
+						dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
+					}
+				}
+
 				$res = true;
 				$from = $this->smtps->getFrom('org');
 				if ($res && !$from) {
@@ -860,7 +918,6 @@ class CMailFile
 					}
 
 					$result = $this->smtps->sendMsg();
-					//print $result;
 
 					if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
 						$this->dump_mail();
@@ -906,9 +963,62 @@ class CMailFile
 				if (!empty($conf->global->$keyforsmtpid)) {
 					$this->transport->setUsername($conf->global->$keyforsmtpid);
 				}
-				if (!empty($conf->global->$keyforsmtppw)) {
+				if (!empty($conf->global->$keyforsmtppw) && getDolGlobalString($keyforsmtpauthtype) != "XOAUTH2") {
 					$this->transport->setPassword($conf->global->$keyforsmtppw);
 				}
+				if (getDolGlobalString($keyforsmtpauthtype) === "XOAUTH2") {
+					require_once DOL_DOCUMENT_ROOT.'/core/lib/oauth.lib.php'; // define $supportedoauth2array
+					$keyforsupportedoauth2array = getDolGlobalString($keyforsmtpoauthservice);
+					if (preg_match('/^.*-/', $keyforsupportedoauth2array)) {
+						$keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array);
+					} else {
+						$keyforprovider = '';
+					}
+					$keyforsupportedoauth2array = preg_replace('/-.*$/', '', $keyforsupportedoauth2array);
+					$keyforsupportedoauth2array = 'OAUTH_'.$keyforsupportedoauth2array.'_NAME';
+
+					$OAUTH_SERVICENAME = (empty($supportedoauth2array[$keyforsupportedoauth2array]['name']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['name'].($keyforprovider ? '-'.$keyforprovider : ''));
+
+					require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php';
+
+					$storage = new DoliStorage($db, $conf);
+
+					try {
+						$tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME);
+						$expire = false;
+						// Is token expired or will token expire in the next 30 seconds
+						if (is_object($tokenobj)) {
+							$expire = ($tokenobj->getEndOfLife() !== -9002 && $tokenobj->getEndOfLife() !== -9001 && time() > ($tokenobj->getEndOfLife() - 30));
+						}
+						// Token expired so we refresh it
+						if (is_object($tokenobj) && $expire) {
+							$credentials = new Credentials(
+								getDolGlobalString('OAUTH_'.getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE').'_ID'),
+								getDolGlobalString('OAUTH_'.getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE').'_SECRET'),
+								getDolGlobalString('OAUTH_'.getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE').'_URLAUTHORIZE')
+							);
+							$serviceFactory = new \OAuth\ServiceFactory();
+							$oauthname = explode('-', $OAUTH_SERVICENAME);
+							// ex service is Google-Emails we need only the first part Google
+							$apiService = $serviceFactory->createService($oauthname[0], $credentials, $storage, array());
+							// il faut sauvegarder le refresh token car google ne le donne qu'une seule fois
+							$refreshtoken = $tokenobj->getRefreshToken();
+							$tokenobj = $apiService->refreshAccessToken($tokenobj);
+							$tokenobj->setRefreshToken($refreshtoken);
+							$storage->storeAccessToken($OAUTH_SERVICENAME, $tokenobj);
+						}
+						if (is_object($tokenobj)) {
+							$this->transport->setAuthMode('XOAUTH2');
+							$this->transport->setPassword($tokenobj->getAccessToken());
+						} else {
+							$this->errors[] = "Token not found";
+						}
+					} catch (Exception $e) {
+						// Return an error if token not found
+						$this->errors[] = $e->getMessage();
+						dol_syslog("CMailFile::sendfile: mail end error=".$e->getMessage(), LOG_ERR);
+					}
+				}
 				if (!empty($conf->global->$keyforsslseflsigned)) {
 					$this->transport->setStreamOptions(array('ssl' => array('allow_self_signed' => true, 'verify_peer' => false)));
 				}
@@ -941,16 +1051,16 @@ class CMailFile
 				try {
 					$result = $this->mailer->send($this->message, $failedRecipients);
 				} catch (Exception $e) {
-					$this->error = $e->getMessage();
+					$this->errors[] = $e->getMessage();
 				}
 				if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
 					$this->dump_mail();
 				}
 
 				$res = true;
-				if (!empty($this->error) || !$result) {
+				if (!empty($this->error) || !empty($this->errors) || !$result) {
 					if (!empty($failedRecipients)) {
-						$this->error = 'Transport failed for the following addresses: "' . join('", "', $failedRecipients) . '".';
+						$this->errors[] = 'Transport failed for the following addresses: "' . join('", "', $failedRecipients) . '".';
 					}
 					dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR);
 					$res = false;

+ 3 - 3
htdocs/core/class/comment.class.php

@@ -71,19 +71,19 @@ class Comment extends CommonObject
 	public $fk_user_modif;
 
 	/**
-	 * @var int Entity
+	 * @var int 		Entity
 	 */
 	public $entity;
 
 	/**
-	 * @var string import key
+	 * @var string 		Import key
 	 */
 	public $import_key;
 
 	public $comments = array();
 
 	/**
-	 * @var Comment Object oldcopy
+	 * @var Comment 	Object oldcopy
 	 */
 	public $oldcopy;
 

+ 24 - 15
htdocs/core/class/commonobject.class.php

@@ -141,7 +141,7 @@ abstract class CommonObject
 	public $linkedObjectsFullLoaded = array();
 
 	/**
-	 * @var Object      To store a cloned copy of object before to edit it and keep track of old properties
+	 * @var CommonObject To store a cloned copy of object before to edit it and keep track of old properties
 	 */
 	public $oldcopy;
 
@@ -3512,7 +3512,7 @@ abstract class CommonObject
 	 *
 	 *	@param	int		$exclspec          	>0 = Exclude special product (product_type=9)
 	 *  @param  string	$roundingadjust    	'none'=Do nothing, 'auto'=Use default method (MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND if defined, or '0'), '0'=Force mode Total of rounding, '1'=Force mode Rounding of total
-	 *  @param	int		$nodatabaseupdate	1=Do not update database. Update only properties of object.
+	 *  @param	int		$nodatabaseupdate	1=Do not update database total fields of the main object. Update only properties in memory. Can be used to save SQL when this method is called several times, so we can do it only once at end.
 	 *  @param	Societe	$seller				If roundingadjust is '0' or '1' or maybe 'auto', it means we recalculate total for lines before calculating total for object and for this, we need seller object (used to analyze lines to check corrupted data).
 	 *	@return	int    			           	<0 if KO, >0 if OK
 	 */
@@ -3558,10 +3558,6 @@ abstract class CommonObject
 
 		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
 
-		if ($roundingadjust == '-1') {
-			$roundingadjust = 'auto'; // For backward compatibility
-		}
-
 		$forcedroundingmode = $roundingadjust;
 		if ($forcedroundingmode == 'auto' && isset($conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND)) {
 			$forcedroundingmode = $conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND;
@@ -3573,7 +3569,7 @@ abstract class CommonObject
 
 		$multicurrency_tx = !empty($this->multicurrency_tx) ? $this->multicurrency_tx : 1;
 
-		// Define constants to find lines to sum
+		// Define constants to find lines to sum (field name int the table_element_line not into table_element)
 		$fieldtva = 'total_tva';
 		$fieldlocaltax1 = 'total_localtax1';
 		$fieldlocaltax2 = 'total_localtax2';
@@ -3609,6 +3605,7 @@ abstract class CommonObject
 		$sql .= ' ORDER by rowid'; // We want to be sure to always use same order of line to not change lines differently when option MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND is used
 
 		dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
+
 		$resql = $this->db->query($sql);
 		if ($resql) {
 			$this->total_ht  = 0;
@@ -3637,14 +3634,26 @@ abstract class CommonObject
 					$localtax_array = array($obj->localtax1_type, $obj->localtax1_tx, $obj->localtax2_type, $obj->localtax2_tx);
 					$tmpcal = calcul_price_total($obj->qty, $obj->up, $obj->remise_percent, $obj->vatrate, $obj->localtax1_tx, $obj->localtax2_tx, 0, 'HT', $obj->info_bits, $obj->product_type, $seller, $localtax_array, (isset($obj->situation_percent) ? $obj->situation_percent : 100), $multicurrency_tx);
 
-					$diff_when_using_price_ht = price2num($tmpcal[1] - $obj->total_tva, 'MT', 1); // If price was set with tax price adn unit price HT has a low number of digits, then we may have a diff on recalculation from unit price HT.
+					$diff_when_using_price_ht = price2num($tmpcal[1] - $obj->total_tva, 'MT', 1); // If price was set with tax price and unit price HT has a low number of digits, then we may have a diff on recalculation from unit price HT.
 					$diff_on_current_total = price2num($obj->total_ttc - $obj->total_ht - $obj->total_tva - $obj->total_localtax1 - $obj->total_localtax2, 'MT', 1);
-					//var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' =? '.$obj->total_ttc);
+					//var_dump($obj->total_ht.' '.$obj->total_tva.' '.$obj->total_localtax1.' '.$obj->total_localtax2.' => '.$obj->total_ttc);
 					//var_dump($diff_when_using_price_ht.' '.$diff_on_current_total);
 
-					if ($diff_when_using_price_ht && $diff_on_current_total) {
+					if ($diff_on_current_total) {
+						// This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2
+						$sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldtva." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2])." WHERE rowid = ".((int) $obj->rowid);
+						dol_syslog('We found unconsistent data into detailed line (diff_on_current_total = '.$diff_on_current_total.') for line rowid = '.$obj->rowid." (ht=".$obj->total_ht." vat=".$obj->total_tva." tax1=".$obj->total_localtax1." tax2=".$obj->total_localtax2." ttc=".$obj->total_ttc."). We fix the total_vat and total_ttc of line by running sqlfix = ".$sqlfix, LOG_WARNING);
+						$resqlfix = $this->db->query($sqlfix);
+						if (!$resqlfix) {
+							dol_print_error($this->db, 'Failed to update line');
+						}
+						$obj->total_tva = $tmpcal[1];
+						$obj->total_ttc = $tmpcal[2];
+					} elseif ($diff_when_using_price_ht && $roundingadjust == '0') {
+						// After calculation from HT, total is consistent but we have found a difference between VAT part in calculation and into database and
+						// we ask to force the use of rounding on line (like done on calculation) so we force update of line
 						$sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldtva." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2])." WHERE rowid = ".((int) $obj->rowid);
-						dol_syslog('We found unconsistent data into detailed line (diff_when_using_price_ht = '.$diff_when_using_price_ht.' and diff_on_current_total = '.$diff_on_current_total.') for line rowid = '.$obj->rowid." (total vat of line calculated=".$tmpcal[1].", database=".$obj->total_tva."). We fix the total_vat and total_ttc of line by running sqlfix = ".$sqlfix, LOG_WARNING);
+						dol_syslog('We found a line with different rounding data into detailed line (diff_when_using_price_ht = '.$diff_when_using_price_ht.' and diff_on_current_total = '.$diff_on_current_total.') for line rowid = '.$obj->rowid." (total vat of line calculated=".$tmpcal[1].", database=".$obj->total_tva."). We fix the total_vat and total_ttc of line by running sqlfix = ".$sqlfix);
 						$resqlfix = $this->db->query($sqlfix);
 						if (!$resqlfix) {
 							dol_print_error($this->db, 'Failed to update line');
@@ -3725,7 +3734,7 @@ abstract class CommonObject
 
 			$this->db->free($resql);
 
-			// Now update global field total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_*
+			// Now update global fields total_ht, total_ttc, total_tva, total_localtax1, total_localtax2, multicurrency_total_* of main object
 			$fieldht = 'total_ht';
 			$fieldtva = 'tva';
 			$fieldlocaltax1 = 'localtax1';
@@ -6024,7 +6033,7 @@ abstract class CommonObject
 						// Test fetch_array ! is_int($key) because fetch_array result is a mix table with Key as alpha and Key as int (depend db engine)
 						if ($key != 'rowid' && $key != 'tms' && $key != 'fk_member' && !is_int($key)) {
 							// we can add this attribute to object
-							if (!empty($extrafields) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
+							if (!empty($extrafields->attributes[$this->table_element]) && in_array($extrafields->attributes[$this->table_element]['type'][$key], array('date', 'datetime'))) {
 								//var_dump($extrafields->attributes[$this->table_element]['type'][$key]);
 								$this->array_options["options_".$key] = $this->db->jdate($value);
 							} else {
@@ -6037,7 +6046,7 @@ abstract class CommonObject
 
 					// If field is a computed field, value must become result of compute
 					foreach ($tab as $key => $value) {
-						if (!empty($extrafields) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
+						if (!empty($extrafields->attributes[$this->table_element]) && !empty($extrafields->attributes[$this->table_element]['computed'][$key])) {
 							//var_dump($conf->disable_compute);
 							if (empty($conf->disable_compute)) {
 								$this->array_options["options_".$key] = dol_eval($extrafields->attributes[$this->table_element]['computed'][$key], 1, 0, '');
@@ -9954,7 +9963,7 @@ abstract class CommonObject
 			}
 		}
 
-		return $error ? -1 * $error : $ok;
+		return $error ? (-1 * $error) : $ok;
 	}
 
 	/**

+ 18 - 7
htdocs/core/class/html.form.class.php

@@ -5156,8 +5156,9 @@ class Form
                     closeOnEscape: false,
                     buttons: {
                         "'.dol_escape_js($langs->transnoentities($labelbuttonyes)).'": function() {
-                        	var options = "&token='.urlencode(newToken()).'";
+							var options = "token='.urlencode(newToken()).'";
                         	var inputok = '.json_encode($inputok).';	/* List of fields into form */
+							var page = "'.dol_escape_js(!empty($page) ? $page : '').'";
                          	var pageyes = "'.dol_escape_js(!empty($pageyes) ? $pageyes : '').'";
                          	if (inputok.length>0) {
                          		$.each(inputok, function(i, inputname) {
@@ -5174,13 +5175,19 @@ class Form
                          			options += "&" + inputname + "=" + encodeURIComponent(inputvalue);
                          		});
                          	}
-                         	var urljump = pageyes + (pageyes.indexOf("?") < 0 ? "?" : "") + options;
-            				if (pageyes.length > 0) { location.href = urljump; }
+							if (pageyes.length > 0) {
+								var post = $.post(
+									pageyes,
+									options,
+									(data) => {$("body").html(data)}
+								);
+							}
                             $(this).dialog("close");
                         },
                         "'.dol_escape_js($langs->transnoentities($labelbuttonno)).'": function() {
-                        	var options = "&token='.urlencode(newToken()).'";
+                        	var options = "token='.urlencode(newToken()).'";
                          	var inputko = '.json_encode($inputko).';	/* List of fields into form */
+							var page = "'.dol_escape_js(!empty($page) ? $page : '').'";
                          	var pageno="'.dol_escape_js(!empty($pageno) ? $pageno : '').'";
                          	if (inputko.length>0) {
                          		$.each(inputko, function(i, inputname) {
@@ -5191,9 +5198,13 @@ class Form
                          			options += "&" + inputname + "=" + encodeURIComponent(inputvalue);
                          		});
                          	}
-                         	var urljump=pageno + (pageno.indexOf("?") < 0 ? "?" : "") + options;
-                         	//alert(urljump);
-            				if (pageno.length > 0) { location.href = urljump; }
+							if (pageno.length > 0) {
+								var post = $.post(
+									pageno,
+									options,
+									(data) => {$("body").html(data)}
+								);
+							}
                             $(this).dialog("close");
                         }
                     }

+ 5 - 5
htdocs/core/class/html.formmail.class.php

@@ -4,7 +4,7 @@
  * Copyright (C) 2010-2011 Juanjo Menent	    <jmenent@2byte.es>
  * Copyright (C) 2015-2017 Marcos García        <marcosgdf@gmail.com>
  * Copyright (C) 2015-2017 Nicolas ZABOURI      <info@inovea-conseil.com>
- * Copyright (C) 2018-2021 Frédéric France      <frederic.france@netlogic.fr>
+ * Copyright (C) 2018-2022 Frédéric France      <frederic.france@netlogic.fr>
  * Copyright (C) 2022	   Charlene Benke       <charlene@patas-monkey.com>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -401,7 +401,7 @@ class FormMail extends Form
 			// Define output language
 			$outputlangs = $langs;
 			$newlang = '';
-			if (!empty($conf->global->MAIN_MULTILANGS) && empty($newlang)) {
+			if (!empty($conf->global->MAIN_MULTILANGS) && !empty($this->param['langsmodels'])) {
 				$newlang = $this->param['langsmodels'];
 			}
 			if (!empty($newlang)) {
@@ -428,7 +428,7 @@ class FormMail extends Form
 			$keytoavoidconflict = empty($this->trackid) ? '' : '-'.$this->trackid; // this->trackid must be defined
 
 			if (GETPOST('mode', 'alpha') == 'init' || (GETPOST('modelselected') && GETPOST('modelmailselected', 'alpha') && GETPOST('modelmailselected', 'alpha') != '-1')) {
-				if (!empty($arraydefaultmessage->joinfiles) && is_array($this->param['fileinit'])) {
+				if (!empty($arraydefaultmessage->joinfiles) && !empty($this->param['fileinit']) && is_array($this->param['fileinit'])) {
 					foreach ($this->param['fileinit'] as $file) {
 						$this->add_attached_files($file, basename($file), dol_mimetype($file));
 					}
@@ -604,7 +604,7 @@ class FormMail extends Form
 
 						// Also add robot email
 						if (!empty($this->fromalsorobot)) {
-							if (!empty($conf->global->MAIN_MAIL_EMAIL_FROM) && $conf->global->MAIN_MAIL_EMAIL_FROM != $conf->global->MAIN_INFO_SOCIETE_MAIL) {
+							if (!empty($conf->global->MAIN_MAIL_EMAIL_FROM) && getDolGlobalString('MAIN_MAIL_EMAIL_FROM') != getDolGlobalString('MAIN_INFO_SOCIETE_MAIL')) {
 								$liste['robot'] = $conf->global->MAIN_MAIL_EMAIL_FROM;
 								if ($this->frommail) {
 									$liste['robot'] .= ' &lt;'.$conf->global->MAIN_MAIL_EMAIL_FROM.'&gt;';
@@ -1182,7 +1182,7 @@ class FormMail extends Form
 	{
 		global $conf, $langs;
 		//if (! $this->errorstomail) $this->errorstomail=$this->frommail;
-		$errorstomail = (!empty($conf->global->MAIN_MAIL_ERRORS_TO) ? $conf->global->MAIN_MAIL_ERRORS_TO : $this->errorstomail);
+		$errorstomail = getDolGlobalString('MAIN_MAIL_ERRORS_TO', (!empty($this->errorstomail) ? $this->errorstomail : ''));
 		if ($this->witherrorstoreadonly) {
 			$out = '<tr><td>'.$langs->trans("MailErrorsTo").'</td><td>';
 			$out .= '<input type="hidden" id="errorstomail" name="errorstomail" value="'.$errorstomail.'" />';

+ 8 - 4
htdocs/core/class/html.formticket.class.php

@@ -315,7 +315,7 @@ class FormTicket
 
 		// Type
 		print '<tr><td class="titlefield"><span class="fieldrequired"><label for="selecttype_code">'.$langs->trans("TicketTypeRequest").'</span></label></td><td>';
-		$this->selectTypesTickets((GETPOST('type_code', 'alpha') ? GETPOST('type_code', 'alpha') : $this->type_code), 'type_code', '', 2, 0, 0, 0, 'minwidth200');
+		$this->selectTypesTickets((GETPOST('type_code', 'alpha') ? GETPOST('type_code', 'alpha') : $this->type_code), 'type_code', '', 2, 1, 0, 0, 'minwidth200');
 		print '</td></tr>';
 
 		// Group
@@ -324,12 +324,12 @@ class FormTicket
 		if ($public) {
 			$filter = 'public=1';
 		}
-		$this->selectGroupTickets((GETPOST('category_code') ? GETPOST('category_code') : $this->category_code), 'category_code', $filter, 2, 0, 0, 0, 'minwidth200');
+		$this->selectGroupTickets((GETPOST('category_code') ? GETPOST('category_code') : $this->category_code), 'category_code', $filter, 2, 1, 0, 0, 'minwidth200');
 		print '</td></tr>';
 
 		// Severity
 		print '<tr><td><span class="fieldrequired"><label for="selectseverity_code">'.$langs->trans("TicketSeverity").'</span></label></td><td>';
-		$this->selectSeveritiesTickets((GETPOST('severity_code') ? GETPOST('severity_code') : $this->severity_code), 'severity_code', '', 2, 0);
+		$this->selectSeveritiesTickets((GETPOST('severity_code') ? GETPOST('severity_code') : $this->severity_code), 'severity_code', '', 2, 1);
 		print '</td></tr>';
 
 		// Subject
@@ -735,6 +735,7 @@ class FormTicket
 				}
 
 				print '>';
+
 				$value = '&nbsp;';
 				if ($format == 0) {
 					$value = ($maxlength ? dol_trunc($arraytypes['label'], $maxlength) : $arraytypes['label']);
@@ -746,7 +747,7 @@ class FormTicket
 					$value = $arraytypes['code'];
 				}
 
-				print $value;
+				print $value ? $value : '&nbsp;';
 				print '</option>';
 			}
 		}
@@ -841,6 +842,7 @@ class FormTicket
 
 					print '>';
 
+					$value = '';
 					if ($format == 0) {
 						$value = ($maxlength ? dol_trunc($label, $maxlength) : $label);
 					}
@@ -1174,6 +1176,8 @@ class FormTicket
 				}
 
 				print '>';
+
+				$value = '';
 				if ($format == 0) {
 					$value = ($maxlength ? dol_trunc($arrayseverities['label'], $maxlength) : $arrayseverities['label']);
 				}

+ 35 - 8
htdocs/core/class/smtps.class.php

@@ -68,6 +68,11 @@ class SMTPs
 	 */
 	private $_smtpsPW = null;
 
+	/**
+	 * Token in case we use OAUTH2
+	 */
+	private $_smtpsToken = null;
+
 	/**
 	 * Who sent the Message
 	 * This can be defined via a INI file or via a setter method
@@ -565,13 +570,13 @@ class SMTPs
 			}
 
 			// Default authentication method is LOGIN
-			if (empty($conf->global->MAIL_SMTP_AUTH_TYPE)) {
-				$conf->global->MAIL_SMTP_AUTH_TYPE = 'LOGIN';
+			if (empty($conf->global->MAIN_MAIL_SMTPS_AUTH_TYPE)) {
+				$conf->global->MAIN_MAIL_SMTPS_AUTH_TYPE = 'LOGIN';
 			}
 
 			// Send Authentication to Server
 			// Check for errors along the way
-			switch ($conf->global->MAIL_SMTP_AUTH_TYPE) {
+			switch ($conf->global->MAIN_MAIL_SMTPS_AUTH_TYPE) {
 				case 'NONE':
 					// Do not send the 'AUTH type' message. For test purpose, if you don't need authentication, it is better to not enter login/pass into setup.
 					$_retVal = true;
@@ -583,9 +588,10 @@ class SMTPs
 					break;
 				case 'XOAUTH2':
 					// "user=$email\1auth=Bearer $token\1\1"
-					$token = 'xxx';
-					$xxxx = "user=".$this->_smtpsID."\1auth=Bearer ".$token."\1\1";
-					$_retVal = $this->socket_send_str('AUTH XOAUTH2 '.base64_encode($xxxx), '235');
+					$user = $this->_smtpsID;
+					$token = $this->_smtpsToken;
+					$initRes = "user=".$user."\001auth=Bearer ".$token."\001\001";
+					$_retVal = $this->socket_send_str('AUTH XOAUTH2 '.base64_encode($initRes), '235');
 					if (!$_retVal) {
 						$this->_setErr(130, 'Error when asking for AUTH XOAUTH2');
 					}
@@ -631,7 +637,7 @@ class SMTPs
 		// Connect to Server
 		if ($this->socket = $this->_server_connect()) {
 			// If a User ID *and* a password is given, assume Authentication is desired
-			if (!empty($this->_smtpsID) && !empty($this->_smtpsPW)) {
+			if (!empty($this->_smtpsID) && (!empty($this->_smtpsPW) || !empty($this->_smtpsToken))) {
 				// Send the RFC2554 specified EHLO.
 				$_retVal = $this->_server_authenticate();
 			} else {
@@ -923,6 +929,27 @@ class SMTPs
 		return $this->_smtpsPW;
 	}
 
+	/**
+	 * User token for OAUTH2
+	 *
+	 * @param 	string 	$_strToken 	User token
+	 * @return 	void
+	 */
+	public function setToken($_strToken)
+	{
+		$this->_smtpsToken = $_strToken;
+	}
+
+	/**
+	 * Retrieves the User token for OAUTH2
+	 *
+	 * @return 	string 		User token for OAUTH2
+	 */
+	public function getToken()
+	{
+		return $this->_smtpsToken;
+	}
+
 	/**
 	 * Character set used for current message
 	 * Character set is defaulted to 'iso-8859-1';
@@ -1866,7 +1893,7 @@ class SMTPs
 		}
 
 		if (!(substr($server_response, 0, 3) == $response)) {
-			$this->_setErr(120, "Ran into problems sending Mail.\r\nResponse: $server_response");
+			$this->_setErr(120, "Ran into problems sending Mail.\r\nResponse:".$server_response);
 			$_retVal = false;
 		}
 

+ 1 - 0
htdocs/core/class/translate.class.php

@@ -640,6 +640,7 @@ class Translate
 				try {
 					$str = sprintf($str, $param1, $param2, $param3, $param4); // Replace %s and %d except for FormatXXX strings.
 				} catch (Exception $e) {
+					// No exception managed
 				}
 			}
 

+ 4 - 4
htdocs/core/class/utils_diff.class.php

@@ -247,6 +247,7 @@ class Diff
 		$html = '';
 
 		// loop over the lines in the diff
+		$element = 'unknown';
 		foreach ($diff as $line) {
 			// extend the HTML with the line
 			switch ($line[1]) {
@@ -260,10 +261,7 @@ class Diff
 					$element = 'ins';
 					break;
 			}
-			$html .=
-			'<'.$element.'>'
-			. htmlspecialchars($line[0])
-				. '</'.$element.'>';
+			$html .= '<'.$element.'>'.dol_escape_htmltag($line[0]).'</'.$element.'>';
 
 			// extend the HTML with the separator
 			$html .= $separator;
@@ -286,6 +284,8 @@ class Diff
 		// initialise the HTML
 		$html = $indentation."<table class=\"diff\">\n";
 
+		$rightCell = $leftCell = '';
+
 		// loop over the lines in the diff
 		$index = 0;
 		$nbdiff = count($diff);

+ 3 - 1
htdocs/core/filemanagerdol/browser/default/browser.php

@@ -21,10 +21,12 @@
 
 //define('NOTOKENRENEWAL',1); // Disables token renewal
 //require '../../../../main.inc.php';
-require '../../connectors/php/config.php'; // This include the define('NOTOKENRENEWAL',1) and the require main.in.php
+require '../../connectors/php/config.inc.php'; // This include the define('NOTOKENRENEWAL',1) and the require main.in.php
 
 global $Config;
 
+top_httphead();
+
 ?>
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
 <html>

+ 2 - 0
htdocs/core/filemanagerdol/browser/default/frmactualfolder.php

@@ -23,6 +23,8 @@ define('NOTOKENRENEWAL', 1); // Disables token renewal
 
 require '../../../../main.inc.php';
 
+top_httphead();
+
 ?>
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
 <!--

+ 2 - 0
htdocs/core/filemanagerdol/browser/default/frmcreatefolder.php

@@ -25,6 +25,8 @@ require '../../../../main.inc.php';
 
 $langs->load("ecm");
 
+top_httphead();
+
 ?>
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
 <!--

+ 2 - 0
htdocs/core/filemanagerdol/browser/default/frmfolders.php

@@ -23,6 +23,8 @@ define('NOTOKENRENEWAL', 1); // Disables token renewal
 
 require '../../../../main.inc.php';
 
+top_httphead();
+
 ?>
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
 <!--

+ 2 - 0
htdocs/core/filemanagerdol/browser/default/frmresourceslist.php

@@ -23,6 +23,8 @@ define('NOTOKENRENEWAL', 1); // Disables token renewal
 
 require '../../../../main.inc.php';
 
+top_httphead();
+
 ?>
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
 <!--

+ 2 - 0
htdocs/core/filemanagerdol/browser/default/frmupload.php

@@ -23,6 +23,8 @@ define('NOTOKENRENEWAL', 1); // Disables token renewal
 
 require '../../../../main.inc.php';
 
+top_httphead();
+
 ?>
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
 <!--

+ 0 - 131
htdocs/core/filemanagerdol/connectors/php/basexml.php

@@ -1,131 +0,0 @@
-<?php
-/*
- * FCKeditor - The text editor for Internet - http://www.fckeditor.net
- * Copyright (C) 2003-2010 Frederico Caldeira Knabben
- *
- * == BEGIN LICENSE ==
- *
- * Licensed under the terms of any of the following licenses at your
- * choice:
- *
- *  - GNU General Public License Version 2 or later (the "GPL")
- *    https://www.gnu.org/licenses/gpl.html
- *
- *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
- *    https://www.gnu.org/licenses/lgpl.html
- *
- *  - Mozilla Public License Version 1.1 or later (the "MPL")
- *    http://www.mozilla.org/MPL/MPL-1.1.html
- *
- * == END LICENSE ==
- *
- * These functions define the base of the XML response sent by the PHP
- * connector.
- */
-
-/**
- * SetXmlHeaders
- *
- * @return	void
- */
-function SetXmlHeaders()
-{
-	ob_end_clean();
-
-	// Prevent the browser from caching the result.
-	// Date in the past
-	header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
-	// always modified
-	header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
-	// HTTP/1.1
-	header('Cache-Control: no-store, no-cache, must-revalidate');
-	header('Cache-Control: post-check=0, pre-check=0', false);
-	// HTTP/1.0
-	header('Pragma: no-cache');
-
-	// Set the response format.
-	header('Content-Type: text/xml; charset=utf-8');
-}
-
-/**
- * CreateXmlHeader
- *
- * @param string	$command		Command
- * @param string	$resourceType	Resource type
- * @param string	$currentFolder	Current folder
- * @return void
- */
-function CreateXmlHeader($command, $resourceType, $currentFolder)
-{
-	SetXmlHeaders();
-
-	// Create the XML document header.
-	echo '<?xml version="1.0" encoding="utf-8" ?>';
-
-	// Create the main "Connector" node.
-	echo '<Connector command="'.$command.'" resourceType="'.$resourceType.'">';
-
-	// Add the current folder node.
-	echo '<CurrentFolder path="'.ConvertToXmlAttribute($currentFolder).'" url="'.ConvertToXmlAttribute(GetUrlFromPath($resourceType, $currentFolder, $command)).'" />';
-
-	$GLOBALS['HeaderSent'] = true;
-}
-
-/**
- * CreateXmlFooter
- *
- * @return void
- */
-function CreateXmlFooter()
-{
-	echo '</Connector>';
-}
-
-/**
- * SendError
- *
- * @param 	integer $number		Number
- * @param 	string 	$text		Text
- * @return	void
- */
-function SendError($number, $text)
-{
-	if ($_GET['Command'] == 'FileUpload') {
-		SendUploadResults($number, "", "", $text);
-	}
-
-	if (isset($GLOBALS['HeaderSent']) && $GLOBALS['HeaderSent']) {
-		SendErrorNode($number, $text);
-		CreateXmlFooter();
-	} else {
-		SetXmlHeaders();
-
-		dol_syslog('Error: '.$number.' '.$text, LOG_ERR);
-
-		// Create the XML document header
-		echo '<?xml version="1.0" encoding="utf-8" ?>';
-
-		echo '<Connector>';
-
-		SendErrorNode($number, $text);
-
-		echo '</Connector>';
-	}
-	exit;
-}
-
-/**
- * SendErrorNode
- *
- * @param 	integer $number		Number
- * @param	string	$text		Text of error
- * @return 	string				Error node
- */
-function SendErrorNode($number, $text)
-{
-	if ($text) {
-		echo '<Error number="'.$number.'" text="'.htmlspecialchars($text).'" />';
-	} else {
-		echo '<Error number="'.$number.'" />';
-	}
-}

+ 0 - 325
htdocs/core/filemanagerdol/connectors/php/commands.php

@@ -1,325 +0,0 @@
-<?php
-/*
- * FCKeditor - The text editor for Internet - http://www.fckeditor.net
- * Copyright (C) 2003-2010 Frederico Caldeira Knabben
- *
- * == BEGIN LICENSE ==
- *
- * Licensed under the terms of any of the following licenses at your
- * choice:
- *
- *  - GNU General Public License Version 2 or later (the "GPL")
- *    https://www.gnu.org/licenses/gpl.html
- *
- *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
- *    https://www.gnu.org/licenses/lgpl.html
- *
- *  - Mozilla Public License Version 1.1 or later (the "MPL")
- *    http://www.mozilla.org/MPL/MPL-1.1.html
- *
- * == END LICENSE ==
- *
- * This is the File Manager Connector for PHP.
- */
-
-/**
- * GetFolders
- *
- * @param	string	$resourceType		Resource type
- * @param 	string 	$currentFolder		Current folder
- * @return 	void
- */
-function GetFolders($resourceType, $currentFolder)
-{
-	// Map the virtual path to the local server path.
-	$sServerDir = ServerMapFolder($resourceType, $currentFolder, 'GetFolders');
-
-	// Array that will hold the folders names.
-	$aFolders = array();
-
-	$oCurrentFolder = @opendir($sServerDir);
-
-	if ($oCurrentFolder !== false) {
-		while ($sFile = readdir($oCurrentFolder)) {
-			if ($sFile != '.' && $sFile != '..' && is_dir($sServerDir.$sFile)) {
-				$aFolders[] = '<Folder name="'.ConvertToXmlAttribute($sFile).'" />';
-			}
-		}
-		closedir($oCurrentFolder);
-	}
-
-	// Open the "Folders" node.
-	echo "<Folders>";
-
-	natcasesort($aFolders);
-	foreach ($aFolders as $sFolder) {
-		echo $sFolder;
-	}
-
-	// Close the "Folders" node.
-	echo "</Folders>";
-}
-
-/**
- * GetFoldersAndFiles
- *
- * @param	string	$resourceType	Resource type
- * @param	string	$currentFolder	Current folder
- * @return void
- */
-function GetFoldersAndFiles($resourceType, $currentFolder)
-{
-	// Map the virtual path to the local server path.
-	$sServerDir = ServerMapFolder($resourceType, $currentFolder, 'GetFoldersAndFiles');
-
-	// Arrays that will hold the folders and files names.
-	$aFolders = array();
-	$aFiles = array();
-
-	$oCurrentFolder = @opendir($sServerDir);
-
-	if ($oCurrentFolder !== false) {
-		while ($sFile = readdir($oCurrentFolder)) {
-			if ($sFile != '.' && $sFile != '..') {
-				if (is_dir($sServerDir.$sFile)) {
-					$aFolders[] = '<Folder name="'.ConvertToXmlAttribute($sFile).'" />';
-				} else {
-					$iFileSize = @filesize($sServerDir.$sFile);
-					if (!$iFileSize) {
-						$iFileSize = 0;
-					}
-					if ($iFileSize > 0) {
-						$iFileSize = round($iFileSize / 1024);
-						if ($iFileSize < 1) {
-							$iFileSize = 1;
-						}
-					}
-
-					$aFiles[] = '<File name="'.ConvertToXmlAttribute($sFile).'" size="'.$iFileSize.'" />';
-				}
-			}
-		}
-		closedir($oCurrentFolder);
-	}
-
-	// Send the folders
-	natcasesort($aFolders);
-	echo '<Folders>';
-
-	foreach ($aFolders as $sFolder) {
-		echo $sFolder;
-	}
-
-	echo '</Folders>';
-
-	// Send the files
-	natcasesort($aFiles);
-	echo '<Files>';
-
-	foreach ($aFiles as $sFiles) {
-		echo $sFiles;
-	}
-
-	echo '</Files>';
-}
-
-/**
- * Create folder
- *
- * @param   string $resourceType    Resource type
- * @param   string $currentFolder   Current folder
- * @return void
- */
-function CreateFolder($resourceType, $currentFolder)
-{
-	if (!isset($_GET)) {
-		global $_GET;
-	}
-	$sErrorNumber = '0';
-	$sErrorMsg = '';
-
-	if (isset($_GET['NewFolderName'])) {
-		$sNewFolderName = $_GET['NewFolderName'];
-		$sNewFolderName = SanitizeFolderName($sNewFolderName);
-
-		if (strpos($sNewFolderName, '..') !== false) {
-			$sErrorNumber = '102'; // Invalid folder name.
-		} else {
-			// Map the virtual path to the local server path of the current folder.
-			$sServerDir = ServerMapFolder($resourceType, $currentFolder, 'CreateFolder');
-
-			if (is_writable($sServerDir)) {
-				$sServerDir .= $sNewFolderName;
-
-				$sErrorMsg = CreateServerFolder($sServerDir);
-
-				switch ($sErrorMsg) {
-					case '':
-						$sErrorNumber = '0';
-						break;
-					case 'Invalid argument':
-					case 'No such file or directory':
-						$sErrorNumber = '102'; // Path too long.
-						break;
-					default:
-						$sErrorNumber = '110';
-						break;
-				}
-			} else {
-				$sErrorNumber = '103';
-			}
-		}
-	} else {
-		$sErrorNumber = '102';
-	}
-
-	// Create the "Error" node.
-	echo '<Error number="'.$sErrorNumber.'" />';
-}
-
-// @CHANGE
-//function FileUpload( $resourceType, $currentFolder, $sCommand )
-/**
- * FileUpload
- *
- * @param	string	$resourceType	Resource type
- * @param 	string 	$currentFolder	Current folder
- * @param	string	$sCommand		Command
- * @param	string	$CKEcallback	Callback
- * @return	null
- */
-function FileUpload($resourceType, $currentFolder, $sCommand, $CKEcallback = '')
-{
-	if (!isset($_FILES)) {
-		global $_FILES;
-	}
-	$sErrorNumber = '0';
-	$sFileName = '';
-
-	if (isset($_FILES['NewFile']) && !is_null($_FILES['NewFile']['tmp_name'])
-	   // This is for the QuickUpload tab box
-		or (isset($_FILES['upload']) && !is_null($_FILES['upload']['tmp_name']))) {
-		global $Config;
-
-		$oFile = isset($_FILES['NewFile']) ? $_FILES['NewFile'] : $_FILES['upload'];
-
-		// $resourceType should be 'Image';
-		$detectHtml = 0;
-
-		// Map the virtual path to the local server path.
-		$sServerDir = ServerMapFolder($resourceType, $currentFolder, $sCommand);
-
-		// Get the uploaded file name.
-		$sFileName = $oFile['name'];
-
-		//$sFileName = SanitizeFileName($sFileName);
-		$sFileName = dol_sanitizeFileName($sFileName);
-
-		$sOriginalFileName = $sFileName;
-
-		// Get the extension.
-		$sExtension = substr($sFileName, (strrpos($sFileName, '.') + 1));
-		$sExtension = strtolower($sExtension);
-
-		//var_dump($Config);
-		/*
-		if (isset($Config['SecureImageUploads'])) {
-			if (($isImageValid = IsImageValid($oFile['tmp_name'], $sExtension)) === false) {
-				$sErrorNumber = '202';
-			}
-		}
-
-		if (isset($Config['HtmlExtensions'])) {
-			if (!IsHtmlExtension($sExtension, $Config['HtmlExtensions']) &&
-				($detectHtml = DetectHtml($oFile['tmp_name'])) === true) {
-				$sErrorNumber = '202';
-			}
-		}
-		*/
-
-
-		include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
-		//var_dump($sFileName); var_dump(image_format_supported($sFileName));exit;
-		$isImageValid = (image_format_supported($sFileName) >= 0 ? true : false);
-		if (!$isImageValid) {
-			$sErrorNumber = '202';
-		}
-
-
-		// Check if it is an allowed extension.
-		if (!$sErrorNumber) {
-			if (IsAllowedExt($sExtension, $resourceType)) {
-				$iCounter = 0;
-
-				while (true) {
-					$sFilePath = $sServerDir.$sFileName;
-
-					if (is_file($sFilePath)) {
-						$iCounter++;
-						$sFileName = RemoveExtension($sOriginalFileName).'('.$iCounter.').'.$sExtension;
-						$sErrorNumber = '201';
-					} else {
-						include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
-						dol_move_uploaded_file($oFile['tmp_name'], $sFilePath, 0, 0);
-
-						if (is_file($sFilePath)) {
-							if (isset($Config['ChmodOnUpload']) && !$Config['ChmodOnUpload']) {
-								break;
-							}
-
-							$permissions = '0777';
-							if (isset($Config['ChmodOnUpload']) && $Config['ChmodOnUpload']) {
-								$permissions = (string) $Config['ChmodOnUpload'];
-							}
-							$permissionsdec = octdec($permissions);
-							dol_syslog("commands.php permission = ".$permissions." ".$permissionsdec." ".decoct($permissionsdec));
-							$oldumask = umask(0);
-							chmod($sFilePath, $permissionsdec);
-							umask($oldumask);
-						}
-
-						break;
-					}
-				}
-
-				if (file_exists($sFilePath)) {
-					//previous checks failed, try once again
-					if (isset($isImageValid) && $isImageValid === -1 && IsImageValid($sFilePath, $sExtension) === false) {
-						dol_syslog("commands.php IsImageValid is ko");
-						@unlink($sFilePath);
-						$sErrorNumber = '202';
-					} elseif (isset($detectHtml) && $detectHtml === -1 && DetectHtml($sFilePath) === true) {
-						dol_syslog("commands.php DetectHtml is ko");
-						@unlink($sFilePath);
-						$sErrorNumber = '202';
-					}
-				}
-			} else {
-				$sErrorNumber = '202';
-			}
-		}
-	} else {
-		$sErrorNumber = '203';
-	}
-
-
-	$sFileUrl = CombinePaths(GetResourceTypePath($resourceType, $sCommand), $currentFolder);
-	$sFileUrl = CombinePaths($sFileUrl, $sFileName);
-
-
-	// @CHANGE
-	//SendUploadResults( $sErrorNumber, $sFileUrl, $sFileName );
-	if ($CKEcallback == '') {
-		// this line already exists so wrap the if block around it
-		SendUploadResults($sErrorNumber, $sFileUrl, $sFileName);
-	} else {
-		//issue the CKEditor Callback
-		SendCKEditorResults(
-			$CKEcallback,
-			$sFileUrl,
-			($sErrorNumber != 0 ? 'Error '.$sErrorNumber.' upload failed.' : 'Upload Successful')
-		);
-	}
-
-	exit;
-}

+ 0 - 0
htdocs/core/filemanagerdol/connectors/php/config.php → htdocs/core/filemanagerdol/connectors/php/config.inc.php


+ 1048 - 0
htdocs/core/filemanagerdol/connectors/php/connector.lib.php

@@ -0,0 +1,1048 @@
+<?php
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ *  - GNU General Public License Version 2 or later (the "GPL")
+ *    https://www.gnu.org/licenses/gpl.html
+ *
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ *    https://www.gnu.org/licenses/lgpl.html
+ *
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")
+ *    http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * These functions are used by the connector.php script.
+ */
+
+/**
+ * SetXmlHeaders
+ *
+ * @return	void
+ */
+function SetXmlHeaders()
+{
+	ob_end_clean();
+
+	// Prevent the browser from caching the result.
+	// Date in the past
+	header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
+	// always modified
+	header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
+	// HTTP/1.1
+	header('Cache-Control: no-store, no-cache, must-revalidate');
+	header('Cache-Control: post-check=0, pre-check=0', false);
+	// HTTP/1.0
+	header('Pragma: no-cache');
+
+	// Set the response format.
+	header('Content-Type: text/xml; charset=utf-8');
+}
+
+/**
+ * CreateXmlHeader
+ *
+ * @param string	$command		Command
+ * @param string	$resourceType	Resource type
+ * @param string	$currentFolder	Current folder
+ * @return void
+ */
+function CreateXmlHeader($command, $resourceType, $currentFolder)
+{
+	SetXmlHeaders();
+
+	// Create the XML document header.
+	echo '<?xml version="1.0" encoding="utf-8" ?>';
+
+	// Create the main "Connector" node.
+	echo '<Connector command="'.$command.'" resourceType="'.$resourceType.'">';
+
+	// Add the current folder node.
+	echo '<CurrentFolder path="'.ConvertToXmlAttribute($currentFolder).'" url="'.ConvertToXmlAttribute(GetUrlFromPath($resourceType, $currentFolder, $command)).'" />';
+
+	$GLOBALS['HeaderSent'] = true;
+}
+
+/**
+ * CreateXmlFooter
+ *
+ * @return void
+ */
+function CreateXmlFooter()
+{
+	echo '</Connector>';
+}
+
+/**
+ * SendError
+ *
+ * @param 	integer $number		Number
+ * @param 	string 	$text		Text
+ * @return	void
+ */
+function SendError($number, $text)
+{
+	if ($_GET['Command'] == 'FileUpload') {
+		SendUploadResults($number, "", "", $text);
+	}
+
+	if (isset($GLOBALS['HeaderSent']) && $GLOBALS['HeaderSent']) {
+		SendErrorNode($number, $text);
+		CreateXmlFooter();
+	} else {
+		SetXmlHeaders();
+
+		dol_syslog('Error: '.$number.' '.$text, LOG_ERR);
+
+		// Create the XML document header
+		echo '<?xml version="1.0" encoding="utf-8" ?>';
+
+		echo '<Connector>';
+
+		SendErrorNode($number, $text);
+
+		echo '</Connector>';
+	}
+	exit;
+}
+
+/**
+ * SendErrorNode
+ *
+ * @param 	integer $number		Number
+ * @param	string	$text		Text of error
+ * @return 	string				Error node
+ */
+function SendErrorNode($number, $text)
+{
+	if ($text) {
+		echo '<Error number="'.$number.'" text="'.htmlspecialchars($text).'" />';
+	} else {
+		echo '<Error number="'.$number.'" />';
+	}
+}
+
+
+
+/**
+ * GetFolders
+ *
+ * @param	string	$resourceType		Resource type
+ * @param 	string 	$currentFolder		Current folder
+ * @return 	void
+ */
+function GetFolders($resourceType, $currentFolder)
+{
+	// Map the virtual path to the local server path.
+	$sServerDir = ServerMapFolder($resourceType, $currentFolder, 'GetFolders');
+
+	// Array that will hold the folders names.
+	$aFolders = array();
+
+	$oCurrentFolder = @opendir($sServerDir);
+
+	if ($oCurrentFolder !== false) {
+		while ($sFile = readdir($oCurrentFolder)) {
+			if ($sFile != '.' && $sFile != '..' && is_dir($sServerDir.$sFile)) {
+				$aFolders[] = '<Folder name="'.ConvertToXmlAttribute($sFile).'" />';
+			}
+		}
+		closedir($oCurrentFolder);
+	}
+
+	// Open the "Folders" node.
+	echo "<Folders>";
+
+	natcasesort($aFolders);
+	foreach ($aFolders as $sFolder) {
+		echo $sFolder;
+	}
+
+	// Close the "Folders" node.
+	echo "</Folders>";
+}
+
+/**
+ * GetFoldersAndFiles
+ *
+ * @param	string	$resourceType	Resource type
+ * @param	string	$currentFolder	Current folder
+ * @return void
+ */
+function GetFoldersAndFiles($resourceType, $currentFolder)
+{
+	// Map the virtual path to the local server path.
+	$sServerDir = ServerMapFolder($resourceType, $currentFolder, 'GetFoldersAndFiles');
+
+	// Arrays that will hold the folders and files names.
+	$aFolders = array();
+	$aFiles = array();
+
+	$oCurrentFolder = @opendir($sServerDir);
+
+	if ($oCurrentFolder !== false) {
+		while ($sFile = readdir($oCurrentFolder)) {
+			if ($sFile != '.' && $sFile != '..') {
+				if (is_dir($sServerDir.$sFile)) {
+					$aFolders[] = '<Folder name="'.ConvertToXmlAttribute($sFile).'" />';
+				} else {
+					$iFileSize = @filesize($sServerDir.$sFile);
+					if (!$iFileSize) {
+						$iFileSize = 0;
+					}
+					if ($iFileSize > 0) {
+						$iFileSize = round($iFileSize / 1024);
+						if ($iFileSize < 1) {
+							$iFileSize = 1;
+						}
+					}
+
+					$aFiles[] = '<File name="'.ConvertToXmlAttribute($sFile).'" size="'.$iFileSize.'" />';
+				}
+			}
+		}
+		closedir($oCurrentFolder);
+	}
+
+	// Send the folders
+	natcasesort($aFolders);
+	echo '<Folders>';
+
+	foreach ($aFolders as $sFolder) {
+		echo $sFolder;
+	}
+
+	echo '</Folders>';
+
+	// Send the files
+	natcasesort($aFiles);
+	echo '<Files>';
+
+	foreach ($aFiles as $sFiles) {
+		echo $sFiles;
+	}
+
+	echo '</Files>';
+}
+
+/**
+ * Create folder
+ *
+ * @param   string $resourceType    Resource type
+ * @param   string $currentFolder   Current folder
+ * @return void
+ */
+function CreateFolder($resourceType, $currentFolder)
+{
+	if (!isset($_GET)) {
+		global $_GET;
+	}
+	$sErrorNumber = '0';
+	$sErrorMsg = '';
+
+	if (isset($_GET['NewFolderName'])) {
+		$sNewFolderName = $_GET['NewFolderName'];
+		$sNewFolderName = SanitizeFolderName($sNewFolderName);
+
+		if (strpos($sNewFolderName, '..') !== false) {
+			$sErrorNumber = '102'; // Invalid folder name.
+		} else {
+			// Map the virtual path to the local server path of the current folder.
+			$sServerDir = ServerMapFolder($resourceType, $currentFolder, 'CreateFolder');
+
+			if (is_writable($sServerDir)) {
+				$sServerDir .= $sNewFolderName;
+
+				$sErrorMsg = CreateServerFolder($sServerDir);
+
+				switch ($sErrorMsg) {
+					case '':
+						$sErrorNumber = '0';
+						break;
+					case 'Invalid argument':
+					case 'No such file or directory':
+						$sErrorNumber = '102'; // Path too long.
+						break;
+					default:
+						$sErrorNumber = '110';
+						break;
+				}
+			} else {
+				$sErrorNumber = '103';
+			}
+		}
+	} else {
+		$sErrorNumber = '102';
+	}
+
+	// Create the "Error" node.
+	echo '<Error number="'.$sErrorNumber.'" />';
+}
+
+// @CHANGE
+//function FileUpload( $resourceType, $currentFolder, $sCommand )
+/**
+ * FileUpload
+ *
+ * @param	string	$resourceType	Resource type
+ * @param 	string 	$currentFolder	Current folder
+ * @param	string	$sCommand		Command
+ * @param	string	$CKEcallback	Callback
+ * @return	null
+ */
+function FileUpload($resourceType, $currentFolder, $sCommand, $CKEcallback = '')
+{
+	if (!isset($_FILES)) {
+		global $_FILES;
+	}
+	$sErrorNumber = '0';
+	$sFileName = '';
+
+	if (isset($_FILES['NewFile']) && !is_null($_FILES['NewFile']['tmp_name']) || (isset($_FILES['upload']) && !is_null($_FILES['upload']['tmp_name']))) {
+		global $Config;
+
+		$oFile = isset($_FILES['NewFile']) ? $_FILES['NewFile'] : $_FILES['upload'];
+
+		// $resourceType should be 'Image';
+		$detectHtml = 0;
+
+		// Map the virtual path to the local server path.
+		$sServerDir = ServerMapFolder($resourceType, $currentFolder, $sCommand);
+
+		// Get the uploaded file name.
+		$sFileName = $oFile['name'];
+
+		//$sFileName = SanitizeFileName($sFileName);
+		$sFileName = dol_sanitizeFileName($sFileName);
+
+		$sOriginalFileName = $sFileName;
+
+		// Get the extension.
+		$sExtension = substr($sFileName, (strrpos($sFileName, '.') + 1));
+		$sExtension = strtolower($sExtension);
+
+		//var_dump($Config);
+		/*
+		 if (isset($Config['SecureImageUploads'])) {
+		 if (($isImageValid = IsImageValid($oFile['tmp_name'], $sExtension)) === false) {
+		 $sErrorNumber = '202';
+		 }
+		 }
+
+		 if (isset($Config['HtmlExtensions'])) {
+		 if (!IsHtmlExtension($sExtension, $Config['HtmlExtensions']) &&
+		 ($detectHtml = DetectHtml($oFile['tmp_name'])) === true) {
+		 $sErrorNumber = '202';
+		 }
+		 }
+		 */
+
+
+		include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
+		//var_dump($sFileName); var_dump(image_format_supported($sFileName));exit;
+		$isImageValid = (image_format_supported($sFileName) >= 0 ? true : false);
+		if (!$isImageValid) {
+			$sErrorNumber = '202';
+		}
+
+
+		// Check if it is an allowed extension.
+		if (!$sErrorNumber) {
+			if (IsAllowedExt($sExtension, $resourceType)) {
+				$iCounter = 0;
+
+				while (true) {
+					$sFilePath = $sServerDir.$sFileName;
+
+					if (is_file($sFilePath)) {
+						$iCounter++;
+						$sFileName = RemoveExtension($sOriginalFileName).'('.$iCounter.').'.$sExtension;
+						$sErrorNumber = '201';
+					} else {
+						include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
+						dol_move_uploaded_file($oFile['tmp_name'], $sFilePath, 0, 0);
+
+						if (is_file($sFilePath)) {
+							if (isset($Config['ChmodOnUpload']) && !$Config['ChmodOnUpload']) {
+								break;
+							}
+
+							$permissions = '0777';
+							if (isset($Config['ChmodOnUpload']) && $Config['ChmodOnUpload']) {
+								$permissions = (string) $Config['ChmodOnUpload'];
+							}
+							$permissionsdec = octdec($permissions);
+							dol_syslog("connector.lib.php permission = ".$permissions." ".$permissionsdec." ".decoct($permissionsdec));
+							$oldumask = umask(0);
+							chmod($sFilePath, $permissionsdec);
+							umask($oldumask);
+						}
+
+						break;
+					}
+				}
+
+				if (file_exists($sFilePath)) {
+					//previous checks failed, try once again
+					if (isset($isImageValid) && $isImageValid === -1 && IsImageValid($sFilePath, $sExtension) === false) {
+						dol_syslog("connector.lib.php IsImageValid is ko");
+						@unlink($sFilePath);
+						$sErrorNumber = '202';
+					} elseif (isset($detectHtml) && $detectHtml === -1 && DetectHtml($sFilePath) === true) {
+						dol_syslog("connector.lib.php DetectHtml is ko");
+						@unlink($sFilePath);
+						$sErrorNumber = '202';
+					}
+				}
+			} else {
+				$sErrorNumber = '202';
+			}
+		}
+	} else {
+		$sErrorNumber = '203';
+	}
+
+
+	$sFileUrl = CombinePaths(GetResourceTypePath($resourceType, $sCommand), $currentFolder);
+	$sFileUrl = CombinePaths($sFileUrl, $sFileName);
+
+
+	// @CHANGE
+	//SendUploadResults( $sErrorNumber, $sFileUrl, $sFileName );
+	if ($CKEcallback == '') {
+		// this line already exists so wrap the if block around it
+		SendUploadResults($sErrorNumber, $sFileUrl, $sFileName);
+	} else {
+		//issue the CKEditor Callback
+		SendCKEditorResults(
+			$CKEcallback,
+			$sFileUrl,
+			($sErrorNumber != 0 ? 'Error '.$sErrorNumber.' upload failed.' : 'Upload Successful')
+			);
+	}
+
+	exit;
+}
+
+
+
+/**
+ * CombinePaths
+ *
+ * @param   string $sBasePath     sBasePath
+ * @param   string $sFolder       sFolder
+ * @return  string                Combined path
+ */
+function CombinePaths($sBasePath, $sFolder)
+{
+	return RemoveFromEnd($sBasePath, '/').'/'.RemoveFromStart($sFolder, '/');
+}
+
+/**
+ * GetResourceTypePath
+ *
+ * @param 	string		$resourceType	Resource type
+ * @param 	string		$sCommand		Command
+ * @return	string						Config
+ */
+function GetResourceTypePath($resourceType, $sCommand)
+{
+	global $Config;
+
+	if ($sCommand == "QuickUpload") {
+		return $Config['QuickUploadPath'][$resourceType];
+	} else {
+		return $Config['FileTypesPath'][$resourceType];
+	}
+}
+
+/**
+ * GetResourceTypeDirectory
+ *
+ * @param string $resourceType	Resource type
+ * @param string $sCommand		Command
+ * @return string
+ */
+function GetResourceTypeDirectory($resourceType, $sCommand)
+{
+	global $Config;
+	if ($sCommand == "QuickUpload") {
+		if (strlen($Config['QuickUploadAbsolutePath'][$resourceType]) > 0) {
+			return $Config['QuickUploadAbsolutePath'][$resourceType];
+		}
+
+		// Map the "UserFiles" path to a local directory.
+		return Server_MapPath($Config['QuickUploadPath'][$resourceType]);
+	} else {
+		if (strlen($Config['FileTypesAbsolutePath'][$resourceType]) > 0) {
+			return $Config['FileTypesAbsolutePath'][$resourceType];
+		}
+
+		// Map the "UserFiles" path to a local directory.
+		return Server_MapPath($Config['FileTypesPath'][$resourceType]);
+	}
+}
+
+/**
+ * GetUrlFromPath
+ *
+ * @param	string 	$resourceType	Resource type
+ * @param 	string 	$folderPath		Path
+ * @param	string	$sCommand		Command
+ * @return	string					Full url
+ */
+function GetUrlFromPath($resourceType, $folderPath, $sCommand)
+{
+	return CombinePaths(GetResourceTypePath($resourceType, $sCommand), $folderPath);
+}
+
+/**
+ * RemoveExtension
+ *
+ * @param 	string		$fileName	Filename
+ * @return	string					String without extension
+ */
+function RemoveExtension($fileName)
+{
+	return substr($fileName, 0, strrpos($fileName, '.'));
+}
+
+/**
+ * ServerMapFolder
+ *
+ * @param 	string	$resourceType	Resource type
+ * @param 	string	$folderPath		Folder
+ * @param 	string	$sCommand		Command
+ * @return	string
+ */
+function ServerMapFolder($resourceType, $folderPath, $sCommand)
+{
+	// Get the resource type directory.
+	$sResourceTypePath = GetResourceTypeDirectory($resourceType, $sCommand);
+
+	// Ensure that the directory exists.
+	$sErrorMsg = CreateServerFolder($sResourceTypePath);
+	if ($sErrorMsg != '') {
+		SendError(1, "Error creating folder \"{$sResourceTypePath}\" ({$sErrorMsg})");
+	}
+
+	// Return the resource type directory combined with the required path.
+	return CombinePaths($sResourceTypePath, $folderPath);
+}
+
+/**
+ * GetParentFolder
+ *
+ * @param	string	$folderPath		Folder path
+ * @return 	string					Parent folder
+ */
+function GetParentFolder($folderPath)
+{
+	$sPattern = "-[/\\\\][^/\\\\]+[/\\\\]?$-";
+	return preg_replace($sPattern, '', $folderPath);
+}
+
+/**
+ * CreateServerFolder
+ *
+ * @param 	string	$folderPath		Folder
+ * @param 	string	$lastFolder		Folder
+ * @return	string					''=success, error message otherwise
+ */
+function CreateServerFolder($folderPath, $lastFolder = null)
+{
+	global $Config;
+	$sParent = GetParentFolder($folderPath);
+
+	// Ensure the folder path has no double-slashes, or mkdir may fail on certain platforms
+	while (strpos($folderPath, '//') !== false) {
+		$folderPath = str_replace('//', '/', $folderPath);
+	}
+
+	// Check if the parent exists, or create it.
+	if (!empty($sParent) && !file_exists($sParent)) {
+		//prevents agains infinite loop when we can't create root folder
+		if (!is_null($lastFolder) && $lastFolder === $sParent) {
+			return "Can't create $folderPath directory";
+		}
+
+		$sErrorMsg = CreateServerFolder($sParent, $folderPath);
+		if ($sErrorMsg != '') {
+			return $sErrorMsg;
+		}
+	}
+
+	if (!file_exists($folderPath)) {
+		// Turn off all error reporting.
+		error_reporting(0);
+
+		$php_errormsg = '';
+		// Enable error tracking to catch the error.
+		ini_set('track_errors', '1');
+
+		if (isset($Config['ChmodOnFolderCreate']) && !$Config['ChmodOnFolderCreate']) {
+			mkdir($folderPath);
+		} else {
+			$permissions = '0777';
+			if (isset($Config['ChmodOnFolderCreate']) && $Config['ChmodOnFolderCreate']) {
+				$permissions = (string) $Config['ChmodOnFolderCreate'];
+			}
+			$permissionsdec = octdec($permissions);
+			$permissionsdec |= octdec('0111'); // Set x bit required for directories
+			dol_syslog("connector.lib.php permission = ".$permissions." ".$permissionsdec." ".decoct($permissionsdec));
+			// To create the folder with 0777 permissions, we need to set umask to zero.
+			$oldumask = umask(0);
+			mkdir($folderPath, $permissionsdec);
+			umask($oldumask);
+		}
+
+		$sErrorMsg = $php_errormsg;
+
+		// Restore the configurations.
+		ini_restore('track_errors');
+		ini_restore('error_reporting');
+
+		return $sErrorMsg;
+	} else {
+		return '';
+	}
+}
+
+/**
+ * Get Root Path
+ *
+ * @return  string              real path
+ */
+function GetRootPath()
+{
+	if (!isset($_SERVER)) {
+		global $_SERVER;
+	}
+	$sRealPath = realpath('./');
+	// #2124 ensure that no slash is at the end
+	$sRealPath = rtrim($sRealPath, "\\/");
+
+	$sSelfPath = $_SERVER['PHP_SELF'];
+	$sSelfPath = substr($sSelfPath, 0, strrpos($sSelfPath, '/'));
+
+	$sSelfPath = str_replace('/', DIRECTORY_SEPARATOR, $sSelfPath);
+
+	$position = strpos($sRealPath, $sSelfPath);
+
+	// This can check only that this script isn't run from a virtual dir
+	// But it avoids the problems that arise if it isn't checked
+	if ($position === false || $position <> strlen($sRealPath) - strlen($sSelfPath)) {
+		SendError(1, 'Sorry, can\'t map "UserFilesPath" to a physical path. You must set the "UserFilesAbsolutePath" value in "editor/filemanager/connectors/php/config.inc.php".');
+	}
+
+	return substr($sRealPath, 0, $position);
+}
+
+/**
+ *  Emulate the asp Server.mapPath function.
+ *  @param	string		$path		given an url path return the physical directory that it corresponds to
+ *  @return	string					Path
+ */
+function Server_MapPath($path)
+{
+	// This function is available only for Apache
+	if (function_exists('apache_lookup_uri')) {
+		$info = apache_lookup_uri($path);
+		return $info->filename.$info->path_info;
+	}
+
+	// This isn't correct but for the moment there's no other solution
+	// If this script is under a virtual directory or symlink it will detect the problem and stop
+	return GetRootPath().$path;
+}
+
+/**
+ * Is Allowed Extension
+ *
+ * @param   string $sExtension      File extension
+ * @param   string $resourceType    ressource type
+ * @return  boolean                 true or false
+ */
+function IsAllowedExt($sExtension, $resourceType)
+{
+	global $Config;
+	// Get the allowed and denied extensions arrays.
+	$arAllowed = $Config['AllowedExtensions'][$resourceType];
+	$arDenied = $Config['DeniedExtensions'][$resourceType];
+
+	if (count($arAllowed) > 0 && !in_array($sExtension, $arAllowed)) {
+		return false;
+	}
+
+	if (count($arDenied) > 0 && in_array($sExtension, $arDenied)) {
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * Is Allowed Type
+ *
+ * @param   string $resourceType    ressource type
+ * @return  boolean                 true or false
+ */
+function IsAllowedType($resourceType)
+{
+	global $Config;
+	if (!in_array($resourceType, $Config['ConfigAllowedTypes'])) {
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * IsAllowedCommand
+ *
+ * @param   string		$sCommand		Command
+ * @return  boolean						True or false
+ */
+function IsAllowedCommand($sCommand)
+{
+	global $Config;
+
+	if (!in_array($sCommand, $Config['ConfigAllowedCommands'])) {
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * GetCurrentFolder
+ *
+ * @return	string		current folder
+ */
+function GetCurrentFolder()
+{
+	if (!isset($_GET)) {
+		global $_GET;
+	}
+	$sCurrentFolder = isset($_GET['CurrentFolder']) ? GETPOST('CurrentFolder', '', 1) : '/';
+
+	// Check the current folder syntax (must begin and start with a slash).
+	if (!preg_match('|/$|', $sCurrentFolder)) {
+		$sCurrentFolder .= '/';
+	}
+	if (strpos($sCurrentFolder, '/') !== 0) {
+		$sCurrentFolder = '/'.$sCurrentFolder;
+	}
+
+	// Ensure the folder path has no double-slashes
+	while (strpos($sCurrentFolder, '//') !== false) {
+		$sCurrentFolder = str_replace('//', '/', $sCurrentFolder);
+	}
+
+	// Check for invalid folder paths (..)
+	if (strpos($sCurrentFolder, '..') || strpos($sCurrentFolder, "\\")) {
+		SendError(102, '');
+	}
+
+	if (preg_match(",(/\.)|[[:cntrl:]]|(//)|(\\\\)|([\:\*\?\"\<\>\|]),", $sCurrentFolder)) {
+		SendError(102, '');
+	}
+
+	return $sCurrentFolder;
+}
+
+/**
+ * Do a cleanup of the folder name to avoid possible problems
+ *
+ * @param	string	$sNewFolderName		Folder
+ * @return	string						Folder sanitized
+ */
+function SanitizeFolderName($sNewFolderName)
+{
+	$sNewFolderName = stripslashes($sNewFolderName);
+
+	// Remove . \ / | : ? * " < >
+	$sNewFolderName = preg_replace('/\\.|\\\\|\\/|\\||\\:|\\?|\\*|"|<|>|[[:cntrl:]]/', '_', $sNewFolderName);
+
+	return $sNewFolderName;
+}
+
+/**
+ * Do a cleanup of the file name to avoid possible problems
+ *
+ * @param	string	$sNewFileName		Folder
+ * @return	string						Folder sanitized
+ */
+function SanitizeFileName($sNewFileName)
+{
+	global $Config;
+
+	$sNewFileName = stripslashes($sNewFileName);
+
+	// Replace dots in the name with underscores (only one dot can be there... security issue).
+	if ($Config['ForceSingleExtension']) {
+		$sNewFileName = preg_replace('/\\.(?![^.]*$)/', '_', $sNewFileName);
+	}
+
+	// Remove \ / | : ? * " < >
+	$sNewFileName = preg_replace('/\\\\|\\/|\\||\\:|\\?|\\*|"|<|>|[[:cntrl:]]/', '_', $sNewFileName);
+
+	return $sNewFileName;
+}
+
+/**
+ * This is the function that sends the results of the uploading process.
+ *
+ * @param	string		$errorNumber	errorNumber
+ * @param	string		$fileUrl		fileUrl
+ * @param	string		$fileName		fileName
+ * @param	string		$customMsg		customMsg
+ * @return	void
+ */
+function SendUploadResults($errorNumber, $fileUrl = '', $fileName = '', $customMsg = '')
+{
+	// Minified version of the document.domain automatic fix script (#1919).
+	// The original script can be found at _dev/domain_fix_template.js
+	echo <<<EOF
+<script type="text/javascript">
+(function(){var d=document.domain;while (true){try{var A=window.parent.document.domain;break;}catch(e) {};d=d.replace(/.*?(?:\.|$)/,'');if (d.length==0) break;try{document.domain=d;}catch (e){break;}}})();
+EOF;
+
+	if ($errorNumber && $errorNumber != 201) {
+		$fileUrl = "";
+		$fileName = "";
+	}
+
+	$rpl = array('\\' => '\\\\', '"' => '\\"');
+	echo 'console.log('.$errorNumber.');';
+	echo 'window.parent.OnUploadCompleted('.$errorNumber.', "'.strtr($fileUrl, $rpl).'", "'.strtr($fileName, $rpl).'", "'.strtr($customMsg, $rpl).'");';
+	echo '</script>';
+	exit;
+}
+
+
+// @CHANGE
+
+// This is the function that sends the results of the uploading process to CKE.
+/**
+ * SendCKEditorResults
+ *
+ * @param   string  $callback       callback
+ * @param   string  $sFileUrl       sFileUrl
+ * @param   string  $customMsg      customMsg
+ * @return  void
+ */
+function SendCKEditorResults($callback, $sFileUrl, $customMsg = '')
+{
+	echo '<script type="text/javascript">';
+
+	$rpl = array('\\' => '\\\\', '"' => '\\"');
+
+	echo 'window.parent.CKEDITOR.tools.callFunction("'.$callback.'","'.strtr($sFileUrl, $rpl).'", "'.strtr($customMsg, $rpl).'");';
+
+	echo '</script>';
+}
+
+
+
+/**
+ * RemoveFromStart
+ *
+ * @param 	string		$sourceString	Source
+ * @param 	string		$charToRemove	Char to remove
+ * @return	string		Result
+ */
+function RemoveFromStart($sourceString, $charToRemove)
+{
+	$sPattern = '|^'.$charToRemove.'+|';
+	return preg_replace($sPattern, '', $sourceString);
+}
+
+/**
+ * RemoveFromEnd
+ *
+ * @param 	string		$sourceString	Source
+ * @param 	string		$charToRemove	Rhar to remove
+ * @return	string		Result
+ */
+function RemoveFromEnd($sourceString, $charToRemove)
+{
+	$sPattern = '|'.$charToRemove.'+$|';
+	return preg_replace($sPattern, '', $sourceString);
+}
+
+/**
+ * FindBadUtf8
+ *
+ * @param 	string $string		String
+ * @return	boolean
+ */
+function FindBadUtf8($string)
+{
+	$regex = '([\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]';
+	$regex .= '|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2}|(.{1}))';
+
+	$matches = array();
+	while (preg_match('/'.$regex.'/S', $string, $matches)) {
+		if (isset($matches[2])) {
+			return true;
+		}
+		$string = substr($string, strlen($matches[0]));
+	}
+
+	return false;
+}
+
+/**
+ * ConvertToXmlAttribute
+ *
+ * @param 	string		$value		Value
+ * @return	string
+ */
+function ConvertToXmlAttribute($value)
+{
+	if (defined('PHP_OS')) {
+		$os = PHP_OS;
+	} else {
+		$os = php_uname();
+	}
+
+	if (strtoupper(substr($os, 0, 3)) === 'WIN' || FindBadUtf8($value)) {
+		return (utf8_encode(htmlspecialchars($value)));
+	} else {
+		return (htmlspecialchars($value));
+	}
+}
+
+/**
+ * Check whether given extension is in html etensions list
+ *
+ * @param 	string 		$ext				Extension
+ * @param 	array 		$formExtensions		Array of extensions
+ * @return 	boolean
+ */
+function IsHtmlExtension($ext, $formExtensions)
+{
+	if (!$formExtensions || !is_array($formExtensions)) {
+		return false;
+	}
+	$lcaseHtmlExtensions = array();
+	foreach ($formExtensions as $key => $val) {
+		$lcaseHtmlExtensions[$key] = strtolower($val);
+	}
+	return in_array($ext, $lcaseHtmlExtensions);
+}
+
+/**
+ * Detect HTML in the first KB to prevent against potential security issue with
+ * IE/Safari/Opera file type auto detection bug.
+ * Returns true if file contain insecure HTML code at the beginning.
+ *
+ * @param string $filePath absolute path to file
+ * @return boolean
+ */
+function DetectHtml($filePath)
+{
+	$fp = @fopen($filePath, 'rb');
+
+	//open_basedir restriction, see #1906
+	if ($fp === false || !flock($fp, LOCK_SH)) {
+		return -1;
+	}
+
+	$chunk = fread($fp, 1024);
+	flock($fp, LOCK_UN);
+	fclose($fp);
+
+	$chunk = strtolower($chunk);
+
+	if (!$chunk) {
+		return false;
+	}
+
+	$chunk = trim($chunk);
+
+	if (preg_match("/<!DOCTYPE\W*X?HTML/sim", $chunk)) {
+		return true;
+	}
+
+	$tags = array('<body', '<head', '<html', '<img', '<pre', '<script', '<table', '<title');
+
+	foreach ($tags as $tag) {
+		if (false !== strpos($chunk, $tag)) {
+			return true;
+		}
+	}
+
+	//type = javascript
+	if (preg_match('!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk)) {
+		return true;
+	}
+
+	//href = javascript
+	//src = javascript
+	//data = javascript
+	if (preg_match('!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk)) {
+		return true;
+	}
+
+	//url(javascript
+	if (preg_match('!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk)) {
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Check file content.
+ * Currently this function validates only image files.
+ * Returns false if file is invalid.
+ *
+ * @param 	string 	$filePath 		Absolute path to file
+ * @param 	string 	$extension 		File extension
+ * @return 	boolean					True or false
+ */
+function IsImageValid($filePath, $extension)
+{
+	if (!@is_readable($filePath)) {
+		return -1;
+	}
+
+	$imageCheckExtensions = array(
+		'gif',
+		'jpeg',
+		'jpg',
+		'png',
+		'swf',
+		'psd',
+		'bmp',
+		'iff',
+		'tiff',
+		'tif',
+		'swc',
+		'jpc',
+		'jp2',
+		'jpx',
+		'jb2',
+		'xbm',
+		'wbmp'
+	);
+
+	if (!in_array($extension, $imageCheckExtensions)) {
+		return true;
+	}
+
+	if (@getimagesize($filePath) === false) {
+		return false;
+	}
+
+	return true;
+}

+ 4 - 7
htdocs/core/filemanagerdol/connectors/php/connector.php

@@ -19,19 +19,16 @@
  *
  * == END LICENSE ==
  *
- * This is the File Manager Connector for PHP.
+ * This is the File Manager Connector for PHP. It returns a XML file used by browser.php
  */
 
 ob_start();
 
-require 'config.php';
-require 'util.php';
-require 'io.php';
-require 'basexml.php';
-require 'commands.php';
+require 'config.inc.php';	// This include the main.inc.php
+require 'connector.lib.php';
 
 if (!$Config['Enabled']) {
-	SendError(1, 'This connector is disabled. Please check the "editor/filemanager/connectors/php/config.php" file');
+	SendError(1, 'This connector is disabled. Please check the "editor/filemanager/connectors/php/config.inc.php" file');
 }
 
 DoResponse();

+ 0 - 438
htdocs/core/filemanagerdol/connectors/php/io.php

@@ -1,438 +0,0 @@
-<?php
-/*
- * FCKeditor - The text editor for Internet - http://www.fckeditor.net
- * Copyright (C) 2003-2010 Frederico Caldeira Knabben
- *
- * == BEGIN LICENSE ==
- *
- * Licensed under the terms of any of the following licenses at your
- * choice:
- *
- *  - GNU General Public License Version 2 or later (the "GPL")
- *    https://www.gnu.org/licenses/gpl.html
- *
- *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
- *    https://www.gnu.org/licenses/lgpl.html
- *
- *  - Mozilla Public License Version 1.1 or later (the "MPL")
- *    http://www.mozilla.org/MPL/MPL-1.1.html
- *
- * == END LICENSE ==
- *
- * This is the File Manager Connector for PHP.
- */
-
-/**
- * CombinePaths
- *
- * @param   string $sBasePath     sBasePath
- * @param   string $sFolder       sFolder
- * @return  string                Combined path
- */
-function CombinePaths($sBasePath, $sFolder)
-{
-	return RemoveFromEnd($sBasePath, '/').'/'.RemoveFromStart($sFolder, '/');
-}
-/**
- * GetResourceTypePath
- *
- * @param 	string		$resourceType	Resource type
- * @param 	string		$sCommand		Command
- * @return	string						Config
- */
-function GetResourceTypePath($resourceType, $sCommand)
-{
-	global $Config;
-
-	if ($sCommand == "QuickUpload") {
-		return $Config['QuickUploadPath'][$resourceType];
-	} else {
-		return $Config['FileTypesPath'][$resourceType];
-	}
-}
-
-/**
- * GetResourceTypeDirectory
- *
- * @param string $resourceType	Resource type
- * @param string $sCommand		Command
- * @return string
- */
-function GetResourceTypeDirectory($resourceType, $sCommand)
-{
-	global $Config;
-	if ($sCommand == "QuickUpload") {
-		if (strlen($Config['QuickUploadAbsolutePath'][$resourceType]) > 0) {
-			return $Config['QuickUploadAbsolutePath'][$resourceType];
-		}
-
-		// Map the "UserFiles" path to a local directory.
-		return Server_MapPath($Config['QuickUploadPath'][$resourceType]);
-	} else {
-		if (strlen($Config['FileTypesAbsolutePath'][$resourceType]) > 0) {
-			return $Config['FileTypesAbsolutePath'][$resourceType];
-		}
-
-		// Map the "UserFiles" path to a local directory.
-		return Server_MapPath($Config['FileTypesPath'][$resourceType]);
-	}
-}
-
-/**
- * GetUrlFromPath
- *
- * @param	string 	$resourceType	Resource type
- * @param 	string 	$folderPath		Path
- * @param	string	$sCommand		Command
- * @return	string					Full url
- */
-function GetUrlFromPath($resourceType, $folderPath, $sCommand)
-{
-	return CombinePaths(GetResourceTypePath($resourceType, $sCommand), $folderPath);
-}
-
-/**
- * RemoveExtension
- *
- * @param 	string		$fileName	Filename
- * @return	string					String without extension
- */
-function RemoveExtension($fileName)
-{
-	return substr($fileName, 0, strrpos($fileName, '.'));
-}
-/**
- * ServerMapFolder
- *
- * @param 	string	$resourceType	Resource type
- * @param 	string	$folderPath		Folder
- * @param 	string	$sCommand		Command
- * @return	string
- */
-function ServerMapFolder($resourceType, $folderPath, $sCommand)
-{
-	// Get the resource type directory.
-	$sResourceTypePath = GetResourceTypeDirectory($resourceType, $sCommand);
-
-	// Ensure that the directory exists.
-	$sErrorMsg = CreateServerFolder($sResourceTypePath);
-	if ($sErrorMsg != '') {
-		SendError(1, "Error creating folder \"{$sResourceTypePath}\" ({$sErrorMsg})");
-	}
-
-	// Return the resource type directory combined with the required path.
-	return CombinePaths($sResourceTypePath, $folderPath);
-}
-
-/**
- * GetParentFolder
- *
- * @param	string	$folderPath		Folder path
- * @return 	string					Parent folder
- */
-function GetParentFolder($folderPath)
-{
-	$sPattern = "-[/\\\\][^/\\\\]+[/\\\\]?$-";
-	return preg_replace($sPattern, '', $folderPath);
-}
-
-/**
- * CreateServerFolder
- *
- * @param 	string	$folderPath		Folder
- * @param 	string	$lastFolder		Folder
- * @return	string					''=success, error message otherwise
- */
-function CreateServerFolder($folderPath, $lastFolder = null)
-{
-	global $Config;
-	$sParent = GetParentFolder($folderPath);
-
-	// Ensure the folder path has no double-slashes, or mkdir may fail on certain platforms
-	while (strpos($folderPath, '//') !== false) {
-		$folderPath = str_replace('//', '/', $folderPath);
-	}
-
-	// Check if the parent exists, or create it.
-	if (!empty($sParent) && !file_exists($sParent)) {
-		//prevents agains infinite loop when we can't create root folder
-		if (!is_null($lastFolder) && $lastFolder === $sParent) {
-			return "Can't create $folderPath directory";
-		}
-
-		$sErrorMsg = CreateServerFolder($sParent, $folderPath);
-		if ($sErrorMsg != '') {
-			return $sErrorMsg;
-		}
-	}
-
-	if (!file_exists($folderPath)) {
-		// Turn off all error reporting.
-		error_reporting(0);
-
-		$php_errormsg = '';
-		// Enable error tracking to catch the error.
-		ini_set('track_errors', '1');
-
-		if (isset($Config['ChmodOnFolderCreate']) && !$Config['ChmodOnFolderCreate']) {
-			mkdir($folderPath);
-		} else {
-			$permissions = '0777';
-			if (isset($Config['ChmodOnFolderCreate']) && $Config['ChmodOnFolderCreate']) {
-				$permissions = (string) $Config['ChmodOnFolderCreate'];
-			}
-			$permissionsdec = octdec($permissions);
-			$permissionsdec |= octdec('0111'); // Set x bit required for directories
-			dol_syslog("io.php permission = ".$permissions." ".$permissionsdec." ".decoct($permissionsdec));
-			// To create the folder with 0777 permissions, we need to set umask to zero.
-			$oldumask = umask(0);
-			mkdir($folderPath, $permissionsdec);
-			umask($oldumask);
-		}
-
-		$sErrorMsg = $php_errormsg;
-
-		// Restore the configurations.
-		ini_restore('track_errors');
-		ini_restore('error_reporting');
-
-		return $sErrorMsg;
-	} else {
-		return '';
-	}
-}
-
-/**
- * Get Root Path
- *
- * @return  string              real path
- */
-function GetRootPath()
-{
-	if (!isset($_SERVER)) {
-		global $_SERVER;
-	}
-	$sRealPath = realpath('./');
-	// #2124 ensure that no slash is at the end
-	$sRealPath = rtrim($sRealPath, "\\/");
-
-	$sSelfPath = $_SERVER['PHP_SELF'];
-	$sSelfPath = substr($sSelfPath, 0, strrpos($sSelfPath, '/'));
-
-	$sSelfPath = str_replace('/', DIRECTORY_SEPARATOR, $sSelfPath);
-
-	$position = strpos($sRealPath, $sSelfPath);
-
-	// This can check only that this script isn't run from a virtual dir
-	// But it avoids the problems that arise if it isn't checked
-	if ($position === false || $position <> strlen($sRealPath) - strlen($sSelfPath)) {
-		SendError(1, 'Sorry, can\'t map "UserFilesPath" to a physical path. You must set the "UserFilesAbsolutePath" value in "editor/filemanager/connectors/php/config.php".');
-	}
-
-	return substr($sRealPath, 0, $position);
-}
-
-/**
- *  Emulate the asp Server.mapPath function.
- *  @param	string		$path		given an url path return the physical directory that it corresponds to
- *  @return	string					Path
- */
-function Server_MapPath($path)
-{
-	// This function is available only for Apache
-	if (function_exists('apache_lookup_uri')) {
-		$info = apache_lookup_uri($path);
-		return $info->filename.$info->path_info;
-	}
-
-	// This isn't correct but for the moment there's no other solution
-	// If this script is under a virtual directory or symlink it will detect the problem and stop
-	return GetRootPath().$path;
-}
-
-/**
- * Is Allowed Extension
- *
- * @param   string $sExtension      File extension
- * @param   string $resourceType    ressource type
- * @return  boolean                 true or false
- */
-function IsAllowedExt($sExtension, $resourceType)
-{
-	global $Config;
-	// Get the allowed and denied extensions arrays.
-	$arAllowed = $Config['AllowedExtensions'][$resourceType];
-	$arDenied = $Config['DeniedExtensions'][$resourceType];
-
-	if (count($arAllowed) > 0 && !in_array($sExtension, $arAllowed)) {
-		return false;
-	}
-
-	if (count($arDenied) > 0 && in_array($sExtension, $arDenied)) {
-		return false;
-	}
-
-	return true;
-}
-
-/**
- * Is Allowed Type
- *
- * @param   string $resourceType    ressource type
- * @return  boolean                 true or false
- */
-function IsAllowedType($resourceType)
-{
-	global $Config;
-	if (!in_array($resourceType, $Config['ConfigAllowedTypes'])) {
-		return false;
-	}
-
-	return true;
-}
-
-/**
- * IsAllowedCommand
- *
- * @param   string		$sCommand		Command
- * @return  boolean						True or false
- */
-function IsAllowedCommand($sCommand)
-{
-	global $Config;
-
-	if (!in_array($sCommand, $Config['ConfigAllowedCommands'])) {
-		return false;
-	}
-
-	return true;
-}
-
-/**
- * GetCurrentFolder
- *
- * @return	string		current folder
- */
-function GetCurrentFolder()
-{
-	if (!isset($_GET)) {
-		global $_GET;
-	}
-	$sCurrentFolder = isset($_GET['CurrentFolder']) ? GETPOST('CurrentFolder', '', 1) : '/';
-
-	// Check the current folder syntax (must begin and start with a slash).
-	if (!preg_match('|/$|', $sCurrentFolder)) {
-		$sCurrentFolder .= '/';
-	}
-	if (strpos($sCurrentFolder, '/') !== 0) {
-		$sCurrentFolder = '/'.$sCurrentFolder;
-	}
-
-	// Ensure the folder path has no double-slashes
-	while (strpos($sCurrentFolder, '//') !== false) {
-		$sCurrentFolder = str_replace('//', '/', $sCurrentFolder);
-	}
-
-	// Check for invalid folder paths (..)
-	if (strpos($sCurrentFolder, '..') || strpos($sCurrentFolder, "\\")) {
-		SendError(102, '');
-	}
-
-	if (preg_match(",(/\.)|[[:cntrl:]]|(//)|(\\\\)|([\:\*\?\"\<\>\|]),", $sCurrentFolder)) {
-		SendError(102, '');
-	}
-
-	return $sCurrentFolder;
-}
-
-/**
- * Do a cleanup of the folder name to avoid possible problems
- *
- * @param	string	$sNewFolderName		Folder
- * @return	string						Folder sanitized
- */
-function SanitizeFolderName($sNewFolderName)
-{
-	$sNewFolderName = stripslashes($sNewFolderName);
-
-	// Remove . \ / | : ? * " < >
-	$sNewFolderName = preg_replace('/\\.|\\\\|\\/|\\||\\:|\\?|\\*|"|<|>|[[:cntrl:]]/', '_', $sNewFolderName);
-
-	return $sNewFolderName;
-}
-
-/**
- * Do a cleanup of the file name to avoid possible problems
- *
- * @param	string	$sNewFileName		Folder
- * @return	string						Folder sanitized
- */
-function SanitizeFileName($sNewFileName)
-{
-	global $Config;
-
-	$sNewFileName = stripslashes($sNewFileName);
-
-	// Replace dots in the name with underscores (only one dot can be there... security issue).
-	if ($Config['ForceSingleExtension']) {
-		$sNewFileName = preg_replace('/\\.(?![^.]*$)/', '_', $sNewFileName);
-	}
-
-	// Remove \ / | : ? * " < >
-	$sNewFileName = preg_replace('/\\\\|\\/|\\||\\:|\\?|\\*|"|<|>|[[:cntrl:]]/', '_', $sNewFileName);
-
-	return $sNewFileName;
-}
-
-/**
- * This is the function that sends the results of the uploading process.
- *
- * @param	string		$errorNumber	errorNumber
- * @param	string		$fileUrl		fileUrl
- * @param	string		$fileName		fileName
- * @param	string		$customMsg		customMsg
- * @return	void
- */
-function SendUploadResults($errorNumber, $fileUrl = '', $fileName = '', $customMsg = '')
-{
-	// Minified version of the document.domain automatic fix script (#1919).
-	// The original script can be found at _dev/domain_fix_template.js
-	echo <<<EOF
-<script type="text/javascript">
-(function(){var d=document.domain;while (true){try{var A=window.parent.document.domain;break;}catch(e) {};d=d.replace(/.*?(?:\.|$)/,'');if (d.length==0) break;try{document.domain=d;}catch (e){break;}}})();
-EOF;
-
-	if ($errorNumber && $errorNumber != 201) {
-		$fileUrl = "";
-		$fileName = "";
-	}
-
-	$rpl = array('\\' => '\\\\', '"' => '\\"');
-	echo 'console.log('.$errorNumber.');';
-	echo 'window.parent.OnUploadCompleted('.$errorNumber.', "'.strtr($fileUrl, $rpl).'", "'.strtr($fileName, $rpl).'", "'.strtr($customMsg, $rpl).'");';
-	echo '</script>';
-	exit;
-}
-
-
-// @CHANGE
-
-// This is the function that sends the results of the uploading process to CKE.
-/**
- * SendCKEditorResults
- *
- * @param   string  $callback       callback
- * @param   string  $sFileUrl       sFileUrl
- * @param   string  $customMsg      customMsg
- * @return  void
- */
-function SendCKEditorResults($callback, $sFileUrl, $customMsg = '')
-{
-	echo '<script type="text/javascript">';
-
-	$rpl = array('\\' => '\\\\', '"' => '\\"');
-
-	echo 'window.parent.CKEDITOR.tools.callFunction("'.$callback.'","'.strtr($sFileUrl, $rpl).'", "'.strtr($customMsg, $rpl).'");';
-
-	echo '</script>';
-}

+ 0 - 75
htdocs/core/filemanagerdol/connectors/php/upload.php

@@ -1,75 +0,0 @@
-<?php
-/*
- * FCKeditor - The text editor for Internet - http://www.fckeditor.net
- * Copyright (C) 2003-2010 Frederico Caldeira Knabben
- *
- * == BEGIN LICENSE ==
- *
- * Licensed under the terms of any of the following licenses at your
- * choice:
- *
- *  - GNU General Public License Version 2 or later (the "GPL")
- *    https://www.gnu.org/licenses/gpl.html
- *
- *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
- *    https://www.gnu.org/licenses/lgpl.html
- *
- *  - Mozilla Public License Version 1.1 or later (the "MPL")
- *    http://www.mozilla.org/MPL/MPL-1.1.html
- *
- * == END LICENSE ==
- *
- * This is the "File Uploader" for PHP.
- */
-
-require 'config.php';	// This include the main.inc.php
-require 'util.php';
-require 'io.php';
-require 'commands.php';
-
-
-/**
- * SendError
- *
- * @param	integer	$number		Number
- * @param	string	$text		Text
- * @return 	void
- */
-function SendError($number, $text)
-{
-	SendUploadResults($number, '', '', $text);
-}
-
-
-// Check if this uploader has been enabled.
-if (empty($Config['Enabled'])) {
-	SendUploadResults('1', '', '', 'This file uploader is disabled. Please check the "filemanagerdol/connectors/php/config.php" file');
-}
-
-$sCommand = 'QuickUpload';
-
-// The file type (from the QueryString, by default 'File', can be 'Image' or 'Media').
-$sType = GETPOSTISSET('Type') ? GETPOST('Type') : 'File';
-
-$sCurrentFolder = "/";
-
-// Is enabled the upload?
-if (!IsAllowedCommand($sCommand)) {
-	SendUploadResults('1', '', '', 'The ""'.$sCommand.'"" command isn\'t allowed');
-}
-
-// Check if it is an allowed type.
-if (!IsAllowedType($sType)) {
-	SendUploadResults(1, '', '', 'Invalid type specified');
-}
-
-
-
-// @CHANGE
-//FileUpload( $sType, $sCurrentFolder, $sCommand )
-
-// Get the CKEditor Callback
-$CKEcallback = $_GET['CKEditorFuncNum'];
-
-// Get uploaded filr and move it at correct place. Note: Some tests on file name are also included into this function
-FileUpload($sType, $sCurrentFolder, $sCommand, $CKEcallback);

+ 0 - 218
htdocs/core/filemanagerdol/connectors/php/util.php

@@ -1,218 +0,0 @@
-<?php
-/*
- * FCKeditor - The text editor for Internet - http://www.fckeditor.net
- * Copyright (C) 2003-2010 Frederico Caldeira Knabben
- *
- * == BEGIN LICENSE ==
- *
- * Licensed under the terms of any of the following licenses at your
- * choice:
- *
- *  - GNU General Public License Version 2 or later (the "GPL")
- *    https://www.gnu.org/licenses/gpl.html
- *
- *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
- *    https://www.gnu.org/licenses/lgpl.html
- *
- *  - Mozilla Public License Version 1.1 or later (the "MPL")
- *    http://www.mozilla.org/MPL/MPL-1.1.html
- *
- * == END LICENSE ==
- *
- * Utility functions for the File Manager Connector for PHP.
- */
-
-/**
- * RemoveFromStart
- *
- * @param 	string		$sourceString	Source
- * @param 	string		$charToRemove	Char to remove
- * @return	string		Result
- */
-function RemoveFromStart($sourceString, $charToRemove)
-{
-	$sPattern = '|^'.$charToRemove.'+|';
-	return preg_replace($sPattern, '', $sourceString);
-}
-
-/**
- * RemoveFromEnd
- *
- * @param 	string		$sourceString	Source
- * @param 	string		$charToRemove	Rhar to remove
- * @return	string		Result
- */
-function RemoveFromEnd($sourceString, $charToRemove)
-{
-	$sPattern = '|'.$charToRemove.'+$|';
-	return preg_replace($sPattern, '', $sourceString);
-}
-
-/**
- * FindBadUtf8
- *
- * @param 	string $string		String
- * @return	boolean
- */
-function FindBadUtf8($string)
-{
-	$regex = '([\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]';
-	$regex .= '|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2}|(.{1}))';
-
-	$matches = array();
-	while (preg_match('/'.$regex.'/S', $string, $matches)) {
-		if (isset($matches[2])) {
-			return true;
-		}
-		$string = substr($string, strlen($matches[0]));
-	}
-
-	return false;
-}
-
-/**
- * ConvertToXmlAttribute
- *
- * @param 	string		$value		Value
- * @return	string
- */
-function ConvertToXmlAttribute($value)
-{
-	if (defined('PHP_OS')) {
-		$os = PHP_OS;
-	} else {
-		$os = php_uname();
-	}
-
-	if (strtoupper(substr($os, 0, 3)) === 'WIN' || FindBadUtf8($value)) {
-		return (utf8_encode(htmlspecialchars($value)));
-	} else {
-		return (htmlspecialchars($value));
-	}
-}
-
-/**
- * Check whether given extension is in html etensions list
- *
- * @param 	string 		$ext				Extension
- * @param 	array 		$formExtensions		Array of extensions
- * @return 	boolean
- */
-function IsHtmlExtension($ext, $formExtensions)
-{
-	if (!$formExtensions || !is_array($formExtensions)) {
-		return false;
-	}
-	$lcaseHtmlExtensions = array();
-	foreach ($formExtensions as $key => $val) {
-		$lcaseHtmlExtensions[$key] = strtolower($val);
-	}
-	return in_array($ext, $lcaseHtmlExtensions);
-}
-
-/**
- * Detect HTML in the first KB to prevent against potential security issue with
- * IE/Safari/Opera file type auto detection bug.
- * Returns true if file contain insecure HTML code at the beginning.
- *
- * @param string $filePath absolute path to file
- * @return boolean
- */
-function DetectHtml($filePath)
-{
-	$fp = @fopen($filePath, 'rb');
-
-	//open_basedir restriction, see #1906
-	if ($fp === false || !flock($fp, LOCK_SH)) {
-		return -1;
-	}
-
-	$chunk = fread($fp, 1024);
-	flock($fp, LOCK_UN);
-	fclose($fp);
-
-	$chunk = strtolower($chunk);
-
-	if (!$chunk) {
-		return false;
-	}
-
-	$chunk = trim($chunk);
-
-	if (preg_match("/<!DOCTYPE\W*X?HTML/sim", $chunk)) {
-		return true;
-	}
-
-	$tags = array('<body', '<head', '<html', '<img', '<pre', '<script', '<table', '<title');
-
-	foreach ($tags as $tag) {
-		if (false !== strpos($chunk, $tag)) {
-			return true;
-		}
-	}
-
-	//type = javascript
-	if (preg_match('!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk)) {
-		return true;
-	}
-
-	//href = javascript
-	//src = javascript
-	//data = javascript
-	if (preg_match('!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk)) {
-		return true;
-	}
-
-	//url(javascript
-	if (preg_match('!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk)) {
-		return true;
-	}
-
-	return false;
-}
-
-/**
- * Check file content.
- * Currently this function validates only image files.
- * Returns false if file is invalid.
- *
- * @param 	string 	$filePath 		Absolute path to file
- * @param 	string 	$extension 		File extension
- * @return 	boolean					True or false
- */
-function IsImageValid($filePath, $extension)
-{
-	if (!@is_readable($filePath)) {
-		return -1;
-	}
-
-	$imageCheckExtensions = array(
-		'gif',
-		'jpeg',
-		'jpg',
-		'png',
-		'swf',
-		'psd',
-		'bmp',
-		'iff',
-		'tiff',
-		'tif',
-		'swc',
-		'jpc',
-		'jp2',
-		'jpx',
-		'jb2',
-		'xbm',
-		'wbmp'
-	);
-
-	if (!in_array($extension, $imageCheckExtensions)) {
-		return true;
-	}
-
-	if (@getimagesize($filePath) === false) {
-		return false;
-	}
-
-	return true;
-}

+ 1 - 1
htdocs/core/lib/admin.lib.php

@@ -693,7 +693,7 @@ function modules_prepare_head($nbofactivatedmodules, $nboftotalmodules)
 
 	$h = 0;
 	$head = array();
-	$mode = empty($conf->global->MAIN_MODULE_SETUP_ON_LIST_BY_DEFAULT) ? 'commonkanban' : 'common';
+	$mode = empty($conf->global->MAIN_MODULE_SETUP_ON_LIST_BY_DEFAULT) ? 'commonkanban' : $conf->global->MAIN_MODULE_SETUP_ON_LIST_BY_DEFAULT;
 	$head[$h][0] = DOL_URL_ROOT."/admin/modules.php?mode=".$mode;
 	if ($nbofactivatedmodules <= (empty($conf->global->MAIN_MIN_NB_ENABLED_MODULE_FOR_WARNING) ? 1 : $conf->global->MAIN_MIN_NB_ENABLED_MODULE_FOR_WARNING)) {	// If only minimal initial modules enabled)
 		//$head[$h][1] = $form->textwithpicto($langs->trans("AvailableModules"), $desc);

+ 29 - 0
htdocs/core/lib/ftp.lib.php

@@ -276,3 +276,32 @@ function dol_ftp_rmdir($connect_id, $file, $newsection)
 		return @ftp_rmdir($connect_id, $newremotefileiso);
 	}
 }
+
+
+/**
+ * Remove FTP directory
+ *
+ * @param 		resource	$connect_id		Connection handler
+ * @param 		string		$newdir			Dir create
+ * @param 		string		$newsection		$newsection
+ * @return		result
+ */
+function dol_ftp_mkdir($connect_id, $newdir, $newsection)
+{
+
+	global $conf;
+
+	if (!empty($conf->global->FTP_CONNECT_WITH_SFTP)) {
+		$newsection = ssh2_sftp_realpath($connect_id, ".").'/./'; // workaround for bug https://bugs.php.net/bug.php?id=64169
+	}
+
+	// Remote file
+	$newremotefileiso = $newsection.(preg_match('@[\\\/]$@', $newsection) ? '' : '/').$newdir;
+	$newremotefileiso = utf8_decode($newremotefileiso);
+
+	if (!empty($conf->global->FTP_CONNECT_WITH_SFTP)) {
+		return ssh2_sftp_mkdir($connect_id, $newremotefileiso, 0777);
+	} else {
+		return @ftp_mkdir($connect_id, $newremotefileiso);
+	}
+}

+ 17 - 9
htdocs/core/lib/functions.lib.php

@@ -954,12 +954,15 @@ function sanitizeVal($out = '', $check = 'alphanohtml', $filter = null, $options
 
 				// Restore entity &apos; into &#39; (restricthtml is for html content so we can use html entity)
 				$out = preg_replace('/&apos;/i', "&#39;", $out);
-
-				preg_match_all('/(<img)/i', $out, $reg);
-				if (count($reg[0]) > getDolGlobalInt("MAIN_SECURITY_MAX_IMG_IN_HTML_CONTENT", 1000)) {
-					$out = '';
-				}
 			} while ($oldstringtoclean != $out);
+
+			// Check the limit of external links in a Rich text content. We count '<img' and 'url('
+			$reg = array();
+			preg_match_all('/(<img|url\()/i', $out, $reg);
+			if (count($reg[0]) > getDolGlobalInt("MAIN_SECURITY_MAX_IMG_IN_HTML_CONTENT", 1000)) {
+				return 'TooManyLinksIntoHTMLString';
+			}
+
 			break;
 		case 'custom':
 			if (empty($filter)) {
@@ -6821,10 +6824,11 @@ function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes
 				for ($attrs = $els->item($i)->attributes, $ii = $attrs->length - 1; $ii >= 0; $ii--) {
 					//var_dump($attrs->item($ii));
 					if (! empty($attrs->item($ii)->name)) {
-						// Delete attribute if not into allowed_attributes
 						if (! in_array($attrs->item($ii)->name, $allowed_attributes)) {
+							// Delete attribute if not into allowed_attributes
 							$els->item($i)->removeAttribute($attrs->item($ii)->name);
 						} elseif (in_array($attrs->item($ii)->name, array('style'))) {
+							// If attribute is 'style'
 							$valuetoclean = $attrs->item($ii)->value;
 
 							if (isset($valuetoclean)) {
@@ -6833,10 +6837,14 @@ function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes
 									$valuetoclean = preg_replace('/\/\*.*\*\//m', '', $valuetoclean);	// clean css comments
 									$valuetoclean = preg_replace('/position\s*:\s*[a-z]+/mi', '', $valuetoclean);
 									if ($els->item($i)->tagName == 'a') {	// more paranoiac cleaning for clickable tags.
-										$valuetoclean = preg_replace('/display\s*://m', '', $valuetoclean);
-										$valuetoclean = preg_replace('/z-index\s*://m', '', $valuetoclean);
-										$valuetoclean = preg_replace('/\s+(top|left|right|bottom)\s*://m', '', $valuetoclean);
+										$valuetoclean = preg_replace('/display\s*:/mi', '', $valuetoclean);
+										$valuetoclean = preg_replace('/z-index\s*:/mi', '', $valuetoclean);
+										$valuetoclean = preg_replace('/\s+(top|left|right|bottom)\s*:/mi', '', $valuetoclean);
 									}
+
+									// We do not allow logout|passwordforgotten.php and action= into the content of a "style" tag
+									$valuetoclean = preg_replace('/(logout|passwordforgotten)\.php/mi', '', $valuetoclean);
+									$valuetoclean = preg_replace('/action=/mi', '', $valuetoclean);
 								} while ($oldvaluetoclean != $valuetoclean);
 							}
 

+ 24 - 5
htdocs/core/lib/oauth.lib.php

@@ -23,16 +23,30 @@
  */
 
 
+$shortscopegoogle = 'userinfo_email,userinfo_profile';
+$shortscopegoogle .= ',openid,email,profile';	// For openid connect
+if (!empty($conf->printing->enabled)) {
+	$shortscopegoogle .= ',cloud_print';
+}
+if (!empty($conf->global->OAUTH_GOOGLE_GSUITE)) {
+	$shortscopegoogle .= ',admin_directory_user';
+}
+if (!empty($conf->global->OAUTH_GOOGLE_GMAIL)) {
+	$shortscopegoogle.=',gmail_full';
+}
+
 // Supported OAUTH (a provider is supported when a file xxx_oauthcallback.php is available into htdocs/core/modules/oauth)
 $supportedoauth2array = array(
-	'OAUTH_GOOGLE_NAME'=>array('callbackfile' => 'google', 'picto' => 'google', 'urlforapp' => 'OAUTH_GOOGLE_DESC', 'name'=>'Google', 'urlforcredentials'=>'https://console.developers.google.com/'),
+	'OAUTH_GOOGLE_NAME'=>array('callbackfile' => 'google', 'picto' => 'google', 'urlforapp' => 'OAUTH_GOOGLE_DESC', 'name'=>'Google', 'urlforcredentials'=>'https://console.developers.google.com/', 'defaultscope'=>$shortscopegoogle),
 );
 if (!empty($conf->stripe->enabled)) {
-	$supportedoauth2array['OAUTH_STRIPE_TEST_NAME'] = array('callbackfile' => 'stripetest', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeTest', 'urlforcredentials'=>'');
-	$supportedoauth2array['OAUTH_STRIPE_LIVE_NAME'] = array('callbackfile' => 'stripelive', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeLive', 'urlforcredentials'=>'');
+	$supportedoauth2array['OAUTH_STRIPE_TEST_NAME'] = array('callbackfile' => 'stripetest', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeTest', 'urlforcredentials'=>'', 'defaultscope'=>'read_write');
+	$supportedoauth2array['OAUTH_STRIPE_LIVE_NAME'] = array('callbackfile' => 'stripelive', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeLive', 'urlforcredentials'=>'', 'defaultscope'=>'read_write');
+}
+$supportedoauth2array['OAUTH_GITHUB_NAME'] = array('callbackfile' => 'github', 'picto' => 'github', 'urlforapp' => 'OAUTH_GITHUB_DESC', 'name'=>'GitHub', 'urlforcredentials'=>'https://github.com/settings/developers', 'defaultscope'=>'user,public_repo');
+if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 2) {
+	$supportedoauth2array['OAUTH_OTHER_NAME'] = array('callbackfile' => 'generic', 'picto' => 'generic', 'urlforapp' => 'OAUTH_OTHER_DESC', 'name'=>'Other', 'urlforcredentials'=>'', 'defaultscope'=>'ToComplete');
 }
-$supportedoauth2array['OAUTH_GITHUB_NAME'] = array('callbackfile' => 'github', 'picto' => 'github', 'urlforapp' => 'OAUTH_GITHUB_DESC', 'name'=>'GitHub', 'urlforcredentials'=>'https://github.com/settings/developers');
-
 
 
 // API access parameters OAUTH
@@ -259,6 +273,11 @@ $list = array(
 		'OAUTH_YAMMER_ID',
 		'OAUTH_YAMMER_SECRET',
 	),
+	array(
+		'OAUTH_OTHER_NAME',
+		'OAUTH_OTHER_ID',
+		'OAUTH_OTHER_SECRET',
+	),
 );
 
 

+ 59 - 61
htdocs/core/modules/facture/doc/pdf_sponge.modules.php

@@ -116,30 +116,30 @@ class pdf_sponge extends ModelePDFFactures
 	public $marge_basse;
 
 
-    /**
-     * @var int heightforinfotot
-     */
-    public $heightforinfotot;
-
-    /**
-     * @var int heightforfreetext
-     */
-    public $heightforfreetext;
-
-    /**
-     * @var int heightforfooter
-     */
-    public $heightforfooter;
-
-    /**
-     * @var int tab_top
-     */
-    public $tab_top;
-
-    /**
-     * @var int tab_top_newpage
-     */
-    public $tab_top_newpage;
+	/**
+	 * @var int heightforinfotot
+	 */
+	public $heightforinfotot;
+
+	/**
+	 * @var int heightforfreetext
+	 */
+	public $heightforfreetext;
+
+	/**
+	 * @var int heightforfooter
+	 */
+	public $heightforfooter;
+
+	/**
+	 * @var int tab_top
+	 */
+	public $tab_top;
+
+	/**
+	 * @var int tab_top_newpage
+	 */
+	public $tab_top_newpage;
 
 	/**
 	 * Issuer
@@ -1243,37 +1243,37 @@ class pdf_sponge extends ModelePDFFactures
 			if (empty($object->mode_reglement_code)
 				&& empty($conf->global->FACTURE_CHQ_NUMBER)
 				&& empty($conf->global->FACTURE_RIB_NUMBER)) {
-					$this->error = $outputlangs->transnoentities("ErrorNoPaiementModeConfigured");
+				$this->error = $outputlangs->transnoentities("ErrorNoPaiementModeConfigured");
 			} elseif (($object->mode_reglement_code == 'CHQ' && empty($conf->global->FACTURE_CHQ_NUMBER) && empty($object->fk_account) && empty($object->fk_bank))
-					|| ($object->mode_reglement_code == 'VIR' && empty($conf->global->FACTURE_RIB_NUMBER) && empty($object->fk_account) && empty($object->fk_bank))) {
-					// Avoid having any valid PDF with setup that is not complete
-					$outputlangs->load("errors");
-
-					$pdf->SetXY($this->marge_gauche, $posy);
-					$pdf->SetTextColor(200, 0, 0);
-					$pdf->SetFont('', 'B', $default_font_size - 2);
-					$this->error = $outputlangs->transnoentities("ErrorPaymentModeDefinedToWithoutSetup", $object->mode_reglement_code);
-					$pdf->MultiCell($posxend - $this->marge_gauche, 3, $this->error, 0, 'L', 0);
-					$pdf->SetTextColor(0, 0, 0);
+				|| ($object->mode_reglement_code == 'VIR' && empty($conf->global->FACTURE_RIB_NUMBER) && empty($object->fk_account) && empty($object->fk_bank))) {
+				// Avoid having any valid PDF with setup that is not complete
+				$outputlangs->load("errors");
+
+				$pdf->SetXY($this->marge_gauche, $posy);
+				$pdf->SetTextColor(200, 0, 0);
+				$pdf->SetFont('', 'B', $default_font_size - 2);
+				$this->error = $outputlangs->transnoentities("ErrorPaymentModeDefinedToWithoutSetup", $object->mode_reglement_code);
+				$pdf->MultiCell($posxend - $this->marge_gauche, 3, $this->error, 0, 'L', 0);
+				$pdf->SetTextColor(0, 0, 0);
 
-					$posy = $pdf->GetY() + 1;
+				$posy = $pdf->GetY() + 1;
 			}
 
 				// Show payment mode
 			if (!empty($object->mode_reglement_code)
-					&& $object->mode_reglement_code != 'CHQ'
-					&& $object->mode_reglement_code != 'VIR') {
-					$pdf->SetFont('', 'B', $default_font_size - 2);
-					$pdf->SetXY($this->marge_gauche, $posy);
-					$titre = $outputlangs->transnoentities("PaymentMode").':';
-					$pdf->MultiCell($posxend - $this->marge_gauche, 5, $titre, 0, 'L');
+				&& $object->mode_reglement_code != 'CHQ'
+				&& $object->mode_reglement_code != 'VIR') {
+				$pdf->SetFont('', 'B', $default_font_size - 2);
+				$pdf->SetXY($this->marge_gauche, $posy);
+				$titre = $outputlangs->transnoentities("PaymentMode").':';
+				$pdf->MultiCell($posxend - $this->marge_gauche, 5, $titre, 0, 'L');
 
-					$pdf->SetFont('', '', $default_font_size - 2);
-					$pdf->SetXY($posxval, $posy);
-					$lib_mode_reg = $outputlangs->transnoentities("PaymentType".$object->mode_reglement_code) != ('PaymentType'.$object->mode_reglement_code) ? $outputlangs->transnoentities("PaymentType".$object->mode_reglement_code) : $outputlangs->convToOutputCharset($object->mode_reglement);
-					$pdf->MultiCell($posxend - $posxval, 5, $lib_mode_reg, 0, 'L');
+				$pdf->SetFont('', '', $default_font_size - 2);
+				$pdf->SetXY($posxval, $posy);
+				$lib_mode_reg = $outputlangs->transnoentities("PaymentType".$object->mode_reglement_code) != ('PaymentType'.$object->mode_reglement_code) ? $outputlangs->transnoentities("PaymentType".$object->mode_reglement_code) : $outputlangs->convToOutputCharset($object->mode_reglement);
+				$pdf->MultiCell($posxend - $posxval, 5, $lib_mode_reg, 0, 'L');
 
-					$posy = $pdf->GetY();
+				$posy = $pdf->GetY();
 			}
 
 					// Show online payment link
@@ -1461,13 +1461,12 @@ class pdf_sponge extends ModelePDFFactures
 				if ($posy > $this->page_hauteur - 4 - $this->heightforfooter) {
 					$this->_pagefoot($pdf, $object, $outputlangs, 1);
 					$pdf->addPage();
-                    if (empty($conf->global->MAIN_PDF_DONOTREPEAT_HEAD)) {
-                        $this->_pagehead($pdf, $object, 0, $outputlangs, $outputlangsbis);
-                        $pdf->setY($this->tab_top_newpage);
-                    }
-                    else{
-                        $pdf->setY($this->marge_haute);
-                    }
+					if (empty($conf->global->MAIN_PDF_DONOTREPEAT_HEAD)) {
+						$this->_pagehead($pdf, $object, 0, $outputlangs, $outputlangsbis);
+						$pdf->setY($this->tab_top_newpage);
+					} else {
+						$pdf->setY($this->marge_haute);
+					}
 					$posy = $pdf->GetY();
 				}
 
@@ -1529,13 +1528,12 @@ class pdf_sponge extends ModelePDFFactures
 
 			if ($posy > $this->page_hauteur - 4 - $this->heightforfooter) {
 				$pdf->addPage();
-                if (empty($conf->global->MAIN_PDF_DONOTREPEAT_HEAD)) {
-                    $this->_pagehead($pdf, $object, 0, $outputlangs, $outputlangsbis);
-                    $pdf->setY($this->tab_top_newpage);
-                }
-                else {
-                    $pdf->setY($this->marge_haute);
-                }
+				if (empty($conf->global->MAIN_PDF_DONOTREPEAT_HEAD)) {
+					$this->_pagehead($pdf, $object, 0, $outputlangs, $outputlangsbis);
+					$pdf->setY($this->tab_top_newpage);
+				} else {
+					$pdf->setY($this->marge_haute);
+				}
 
 				$posy = $pdf->GetY();
 			}

+ 193 - 0
htdocs/core/modules/oauth/generic_oauthcallback.php

@@ -0,0 +1,193 @@
+<?php
+/* Copyright (C) 2022       Laurent Destailleur  <eldy@users.sourceforge.net>
+ * Copyright (C) 2015       Frederic France      <frederic.france@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ *      \file       htdocs/core/modules/oauth/generic_oauthcallback.php
+ *      \ingroup    oauth
+ *      \brief      Page to get oauth callback
+ */
+
+require '../../../main.inc.php';
+require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php';
+use OAuth\Common\Storage\DoliStorage;
+use OAuth\Common\Consumer\Credentials;
+use OAuth\OAuth2\Service\GitHub;
+
+// Define $urlwithroot
+$urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
+$urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
+//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
+
+
+$action = GETPOST('action', 'aZ09');
+$backtourl = GETPOST('backtourl', 'alpha');
+$keyforprovider = GETPOST('keyforprovider', 'aZ09');
+if (empty($keyforprovider) && !empty($_SESSION["oauthkeyforproviderbeforeoauthjump"]) && (GETPOST('code') || $action == 'delete')) {
+	$keyforprovider = $_SESSION["oauthkeyforproviderbeforeoauthjump"];
+}
+$genericstring = 'OTHER';
+
+
+/**
+ * Create a new instance of the URI class with the current URI, stripping the query string
+ */
+$uriFactory = new \OAuth\Common\Http\Uri\UriFactory();
+//$currentUri = $uriFactory->createFromSuperGlobalArray($_SERVER);
+//$currentUri->setQuery('');
+$currentUri = $uriFactory->createFromAbsolute($urlwithroot.'/core/modules/oauth/generic_oauthcallback.php');
+
+
+/**
+ * Load the credential for the service
+ */
+
+/** @var $serviceFactory \OAuth\ServiceFactory An OAuth service factory. */
+$serviceFactory = new \OAuth\ServiceFactory();
+$httpClient = new \OAuth\Common\Http\Client\CurlClient();
+// TODO Set options for proxy and timeout
+// $params=array('CURLXXX'=>value, ...)
+//$httpClient->setCurlParameters($params);
+$serviceFactory->setHttpClient($httpClient);
+
+// Dolibarr storage
+$storage = new DoliStorage($db, $conf);
+
+// Setup the credentials for the requests
+$keyforparamid = 'OAUTH_'.$genericstring.($keyforprovider ? '-'.$keyforprovider : '').'_ID';
+$keyforparamsecret = 'OAUTH_'.$genericstring.($keyforprovider ? '-'.$keyforprovider : '').'_SECRET';
+$credentials = new Credentials(
+	getDolGlobalString($keyforparamid),
+	getDolGlobalString($keyforparamsecret),
+	$currentUri->getAbsoluteUri()
+);
+
+$requestedpermissionsarray = array();
+if (GETPOST('state')) {
+	$requestedpermissionsarray = explode(',', GETPOST('state')); // Example: 'user'. 'state' parameter is standard to retrieve some parameters back
+}
+if ($action != 'delete' && empty($requestedpermissionsarray)) {
+	print 'Error, parameter state is not defined';
+	exit;
+}
+//var_dump($requestedpermissionsarray);exit;
+
+// Instantiate the Api service using the credentials, http client and storage mechanism for the token
+$apiService = $serviceFactory->createService($genericstring, $credentials, $storage, $requestedpermissionsarray);
+
+/*
+var_dump($genericstring.($keyforprovider ? '-'.$keyforprovider : ''));
+var_dump($credentials);
+var_dump($storage);
+var_dump($requestedpermissionsarray);
+*/
+
+if (empty($apiService)) {
+	print 'Error, failed to create serviceFactory';
+	exit;
+}
+
+// access type needed to have oauth provider refreshing token
+//$apiService->setAccessType('offline');
+
+$langs->load("oauth");
+
+if (!getDolGlobalString($keyforparamid)) {
+	accessforbidden('Setup of service is not complete. Customer ID is missing');
+}
+if (!getDolGlobalString($keyforparamsecret)) {
+	accessforbidden('Setup of service is not complete. Secret key is missing');
+}
+
+
+/*
+ * Actions
+ */
+
+if ($action == 'delete') {
+	$storage->clearToken($genericstring);
+
+	setEventMessages($langs->trans('TokenDeleted'), null, 'mesgs');
+
+	header('Location: '.$backtourl);
+	exit();
+}
+
+if (GETPOST('code')) {     // We are coming from oauth provider page
+	// We should have
+	//$_GET=array('code' => string 'aaaaaaaaaaaaaa' (length=20), 'state' => string 'user,public_repo' (length=16))
+
+	dol_syslog("We are coming from the oauth provider page");
+	//llxHeader('',$langs->trans("OAuthSetup"));
+
+	//$linkback='<a href="'.DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1">'.$langs->trans("BackToModuleList").'</a>';
+	//print load_fiche_titre($langs->trans("OAuthSetup"),$linkback,'title_setup');
+
+	//print dol_get_fiche_head();
+	// retrieve the CSRF state parameter
+	$state = GETPOSTISSET('state') ? GETPOST('state') : null;
+	//print '<table>';
+
+	// This was a callback request from service, get the token
+	try {
+		//var_dump($_GET['code']);
+		//var_dump($state);
+		//var_dump($apiService);      // OAuth\OAuth2\Service\GitHub
+
+		//$token = $apiService->requestAccessToken(GETPOST('code'), $state);
+		$token = $apiService->requestAccessToken(GETPOST('code'));
+		// Github is a service that does not need state to be stored.
+		// Into constructor of GitHub, the call
+		// parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri)
+		// has not the ending parameter to true like the Google class constructor.
+
+		setEventMessages($langs->trans('NewTokenStored'), null, 'mesgs'); // Stored into object managed by class DoliStorage so into table oauth_token
+
+		$backtourl = $_SESSION["backtourlsavedbeforeoauthjump"];
+		unset($_SESSION["backtourlsavedbeforeoauthjump"]);
+
+		header('Location: '.$backtourl);
+		exit();
+	} catch (Exception $e) {
+		print $e->getMessage();
+	}
+} else { // If entry on page with no parameter, we arrive here
+	$_SESSION["backtourlsavedbeforeoauthjump"] = $backtourl;
+	$_SESSION["oauthkeyforproviderbeforeoauthjump"] = $keyforprovider;
+	$_SESSION['oauthstateanticsrf'] = $state;
+
+	// This may create record into oauth_state before the header redirect.
+	// Creation of record with state in this tables depend on the Provider used (see its constructor).
+	if (GETPOST('state')) {
+		$url = $apiService->getAuthorizationUri(array('state' => GETPOST('state')));
+	} else {
+		$url = $apiService->getAuthorizationUri(); // Parameter state will be randomly generated
+	}
+
+	// we go on oauth provider authorization page
+	header('Location: '.$url);
+	exit();
+}
+
+
+/*
+ * View
+ */
+
+// No view at all, just actions
+
+$db->close();

+ 2 - 2
htdocs/core/modules/oauth/github_oauthcallback.php

@@ -1,5 +1,5 @@
 <?php
-/*
+/* Copyright (C) 2022       Laurent Destailleur  <eldy@users.sourceforge.net>
  * Copyright (C) 2015       Frederic France      <frederic.france@free.fr>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -86,7 +86,7 @@ if ($action != 'delete' && empty($requestedpermissionsarray)) {
 //var_dump($requestedpermissionsarray);exit;
 
 // Instantiate the Api service using the credentials, http client and storage mechanism for the token
-$apiService = $serviceFactory->createService('GitHub'.($keyforprovider ? '-'.$keyforprovider : ''), $credentials, $storage, $requestedpermissionsarray);
+$apiService = $serviceFactory->createService('GitHub', $credentials, $storage, $requestedpermissionsarray);
 
 // access type needed to have oauth provider refreshing token
 //$apiService->setAccessType('offline');

+ 1 - 1
htdocs/core/modules/oauth/google_oauthcallback.php

@@ -1,5 +1,5 @@
 <?php
-/*
+/* Copyright (C) 2022       Laurent Destailleur  <eldy@users.sourceforge.net>
  * Copyright (C) 2015       Frederic France      <frederic.france@free.fr>
  *
  * This program is free software; you can redistribute it and/or modify

+ 1 - 1
htdocs/core/modules/oauth/stripelive_oauthcallback.php

@@ -1,5 +1,5 @@
 <?php
-/*
+/* Copyright (C) 2022       Laurent Destailleur  <eldy@users.sourceforge.net>
  * Copyright (C) 2019       Thibault FOUCART     <support@ptibogxiv.net>
  *
  * This program is free software; you can redistribute it and/or modify

+ 1 - 1
htdocs/core/modules/oauth/stripetest_oauthcallback.php

@@ -1,5 +1,5 @@
 <?php
-/*
+/* Copyright (C) 2022       Laurent Destailleur  <eldy@users.sourceforge.net>
  * Copyright (C) 2015       Frederic France      <frederic.france@free.fr>
  *
  * This program is free software; you can redistribute it and/or modify

+ 0 - 1
htdocs/core/modules/printing/printgcp.modules.php

@@ -137,7 +137,6 @@ class printing_printgcp extends PrintingDriver
 				$this->errors[] = $e->getMessage();
 				$token_ok = false;
 			}
-			//var_dump($this->errors);exit;
 
 			$expire = false;
 			// Is token expired or will token expire in the next 30 seconds

+ 3 - 1
htdocs/core/triggers/interface_50_modTicket_TicketEmail.class.php

@@ -387,10 +387,12 @@ class InterfaceTicketEmail extends DolibarrTriggers
 
 		$trackid = 'tic'.$object->id;
 
+		$old_MAIN_MAIL_AUTOCOPY_TO = getDolGlobalString('MAIN_MAIL_AUTOCOPY_TO');
+
 		if (!empty($conf->global->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_customer, $filepath, $mimetype, $filename, '', '', 0, -1, '', '', $trackid, '', 'ticket');
 		if ($mailfile->error) {

+ 1 - 1
htdocs/document.php

@@ -120,7 +120,7 @@ if ($user->socid > 0) {
 
 // For some module part, dir may be privates
 if (in_array($modulepart, array('facture_paiement', 'unpaid'))) {
-	if (empty($user->rights->societe->client->voir) || $socid) {
+	if (empty($user->hasRight('societe', 'client', 'voir')) || $socid) {
 		$original_file = 'private/'.$user->id.'/'.$original_file; // If user has no permission to see all, output dir is specific to user
 	}
 }

+ 1 - 1
htdocs/emailcollector/class/emailcollector.class.php

@@ -119,7 +119,7 @@ class EmailCollector extends CommonObject
 	public $fields = array(
 		'rowid'         => array('type'=>'integer', 'label'=>'TechnicalID', 'visible'=>2, 'enabled'=>1, 'position'=>1, 'notnull'=>1, 'index'=>1),
 		'entity'        => array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'default'=>1, 'notnull'=>1, 'index'=>1, 'position'=>20),
-		'ref'           => array('type'=>'varchar(128)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>1, 'index'=>1, 'position'=>10, 'searchall'=>1, 'help'=>'Example: MyCollector1', 'csslist'=>'tdoverflowmax200'),
+		'ref'           => array('type'=>'varchar(128)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>1, 'index'=>1, 'position'=>10, 'searchall'=>1, 'help'=>'Example: MyCollector1', 'csslist'=>'tdoverflowmax150'),
 		'label'         => array('type'=>'varchar(255)', 'label'=>'Label', 'visible'=>1, 'enabled'=>1, 'position'=>30, 'notnull'=>-1, 'searchall'=>1, 'help'=>'Example: My Email collector', 'csslist'=>'tdoverflowmax150'),
 		'description'   => array('type'=>'text', 'label'=>'Description', 'visible'=>-1, 'enabled'=>1, 'position'=>60, 'notnull'=>-1, 'csslist'=>'small'),
 		'host'          => array('type'=>'varchar(255)', 'label'=>'EMailHost', 'visible'=>1, 'enabled'=>1, 'position'=>90, 'notnull'=>1, 'searchall'=>1, 'comment'=>"IMAP server", 'help'=>'Example: imap.gmail.com', 'csslist'=>'tdoverflow125'),

+ 2 - 2
htdocs/fichinter/class/fichinter.class.php

@@ -533,7 +533,7 @@ class Fichinter extends CommonObject
 		$resql = $this->db->query($sql);
 		if ($resql) {
 			if (!$error) {
-				$this->oldcopy = clone $this;
+				$this->oldcopy = dol_clone($this);
 			}
 
 			if (!$error) {
@@ -1422,7 +1422,7 @@ class Fichinter extends CommonObject
 			}
 
 			if (!$error) {
-				$this->oldcopy = clone $this;
+				$this->oldcopy = dol_clone($this);
 				$this->ref_client = $ref_client;
 			}
 

+ 36 - 4
htdocs/ftp/index.php

@@ -41,6 +41,7 @@ $result = restrictedArea($user, 'ftp', '');
 // Get parameters
 $action = GETPOST('action', 'aZ09');
 $section = GETPOST('section');
+$newfolder = GETPOST('newfolder');
 if (!$section) {
 	$section = '/';
 }
@@ -141,11 +142,8 @@ if ($action == 'uploadfile') {
 		$mesg = $resultarray['mesg'];
 	}
 	if ($conn_id && $ok && !$mesg) {
-		// var_dump($_FILES['userfile']['name']);
 		$nbfile = count($_FILES['userfile']['name']);
-		$i = 0;
-		for (; $i < $nbfile; $i++) {
-			var_dump($i);
+		for ($i = 0; $i < $nbfile; $i++) {
 			$newsection = $newsectioniso;
 			$fileupload = $_FILES['userfile']['name'][$i];
 			$fileuploadpath = $_FILES['userfile']['tmp_name'][$i];
@@ -164,6 +162,30 @@ if ($action == 'uploadfile') {
 	}
 }
 
+if ($action == 'addfolder') {
+	// set up a connection or die
+	if (!$conn_id) {
+		$newsectioniso = utf8_decode($section);
+		$resultarray = dol_ftp_connect($ftp_server, $ftp_port, $ftp_user, $ftp_password, $newsectioniso, $ftp_passive);
+		$conn_id = $resultarray['conn_id'];
+		$ok = $resultarray['ok'];
+		$mesg = $resultarray['mesg'];
+	}
+	if ($conn_id && $ok && !$mesg) {
+		$result = dol_ftp_mkdir($conn_id, $newfolder, $newsectioniso);
+
+		if ($result) {
+			setEventMessages($langs->trans("FileWasCreateFolder", $newfolder), null, 'mesgs');
+		} else {
+			dol_syslog("ftp/index.php ftp_delete", LOG_ERR);
+			setEventMessages($langs->trans("FTPFailedToCreateFolder", $newfolder), null, 'errors');
+		}
+		$action = '';
+	} else {
+		dol_print_error('', $mesg);
+	}
+}
+
 // Action ajout d'un rep
 if ($action == 'add' && $user->rights->ftp->setup) {
 	$ecmdir->ref                = GETPOST("ref");
@@ -633,6 +655,16 @@ if (!function_exists('ftp_connect')) {
 			print '<td></td>';
 			print '<td align="center"><button type="submit" class="butAction" name="uploadfile" value="'.$langs->trans("Save").'">'.$langs->trans("Upload").'</button></td>';
 			print '</form>';
+			print load_fiche_titre($langs->trans("AddFolder"), null, null);
+			print '<form enctype="multipart/form-data" action="'.$_SERVER["PHP_SELF"].'" method="post">';
+			print '<input type="hidden" name="token" value="'.newToken().'">';
+			print '<input type="hidden" name="numero_ftp" value="'.$numero_ftp.'">';
+			print '<input type="hidden" name="section" value="'.$section.'">';
+			print '<input type="hidden" name="action" value="addfolder">';
+			print '<td><input type="text" class="flat"  name="newfolder" multiple></td>';
+			print '<td></td>';
+			print '<td align="center"><button type="submit" class="butAction" name="addfolder" value="'.$langs->trans("Save").'">'.$langs->trans("AddFolder").'</button></td>';
+			print '</form>';
 		}
 	} else {
 		$foundsetup = false;

+ 2 - 2
htdocs/imports/import.php

@@ -1818,7 +1818,7 @@ if ($step == 5 && $datatoimport) {
 
 		// Actions
 		print '<div class="center">';
-		if ($user->rights->import->run) {
+		if ($user->hasRight('import', 'run')) {
 			print '<input type="submit" class="butAction" value="'.$langs->trans("RunSimulateImportFile").'">';
 		} else {
 			print '<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->transnoentitiesnoconv("NotEnoughPermissions")).'">'.$langs->trans("RunSimulateImportFile").'</a>';
@@ -1974,7 +1974,7 @@ if ($step == 5 && $datatoimport) {
 
 		// Actions
 		print '<div class="center">';
-		if ($user->rights->import->run) {
+		if ($user->hasRight('import', 'run')) {
 			if (empty($nboferrors)) {
 				print '<a class="butAction" href="'.DOL_URL_ROOT.'/imports/import.php?leftmenu=import&step=6&importid='.$importid.$param.'">'.$langs->trans("RunImportFile").'</a>';
 			} else {

+ 85 - 80
htdocs/includes/jquery/js/jquery-ui.js

@@ -1,4 +1,4 @@
-/*! jQuery UI - v1.13.1 - 2022-01-20
+/*! jQuery UI - v1.13.2 - 2022-07-14
 * http://jqueryui.com
 * Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-patch.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js
 * Copyright jQuery Foundation and other contributors; Licensed MIT */
@@ -20,11 +20,11 @@
 
 $.ui = $.ui || {};
 
-var version = $.ui.version = "1.13.1";
+var version = $.ui.version = "1.13.2";
 
 
 /*!
- * jQuery UI Widget 1.13.1
+ * jQuery UI Widget 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -766,7 +766,7 @@ var widget = $.widget;
 
 
 /*!
- * jQuery UI Position 1.13.1
+ * jQuery UI Position 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -1263,7 +1263,7 @@ var position = $.ui.position;
 
 
 /*!
- * jQuery UI :data 1.13.1
+ * jQuery UI :data 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -1292,7 +1292,7 @@ var data = $.extend( $.expr.pseudos, {
 } );
 
 /*!
- * jQuery UI Disable Selection 1.13.1
+ * jQuery UI Disable Selection 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -2047,7 +2047,7 @@ colors = jQuery.Color.names = {
 
 
 /*!
- * jQuery UI Effects 1.13.1
+ * jQuery UI Effects 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -2431,7 +2431,7 @@ if ( $.uiBackCompat !== false ) {
 }
 
 $.extend( $.effects, {
-	version: "1.13.1",
+	version: "1.13.2",
 
 	define: function( name, mode, effect ) {
 		if ( !effect ) {
@@ -2999,7 +2999,7 @@ var effect = $.effects;
 
 
 /*!
- * jQuery UI Effects Blind 1.13.1
+ * jQuery UI Effects Blind 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -3054,7 +3054,7 @@ var effectsEffectBlind = $.effects.define( "blind", "hide", function( options, d
 
 
 /*!
- * jQuery UI Effects Bounce 1.13.1
+ * jQuery UI Effects Bounce 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -3149,7 +3149,7 @@ var effectsEffectBounce = $.effects.define( "bounce", function( options, done )
 
 
 /*!
- * jQuery UI Effects Clip 1.13.1
+ * jQuery UI Effects Clip 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -3199,7 +3199,7 @@ var effectsEffectClip = $.effects.define( "clip", "hide", function( options, don
 
 
 /*!
- * jQuery UI Effects Drop 1.13.1
+ * jQuery UI Effects Drop 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -3253,7 +3253,7 @@ var effectsEffectDrop = $.effects.define( "drop", "hide", function( options, don
 
 
 /*!
- * jQuery UI Effects Explode 1.13.1
+ * jQuery UI Effects Explode 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -3349,7 +3349,7 @@ var effectsEffectExplode = $.effects.define( "explode", "hide", function( option
 
 
 /*!
- * jQuery UI Effects Fade 1.13.1
+ * jQuery UI Effects Fade 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -3381,7 +3381,7 @@ var effectsEffectFade = $.effects.define( "fade", "toggle", function( options, d
 
 
 /*!
- * jQuery UI Effects Fold 1.13.1
+ * jQuery UI Effects Fold 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -3455,7 +3455,7 @@ var effectsEffectFold = $.effects.define( "fold", "hide", function( options, don
 
 
 /*!
- * jQuery UI Effects Highlight 1.13.1
+ * jQuery UI Effects Highlight 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -3497,7 +3497,7 @@ var effectsEffectHighlight = $.effects.define( "highlight", "show", function( op
 
 
 /*!
- * jQuery UI Effects Size 1.13.1
+ * jQuery UI Effects Size 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -3675,7 +3675,7 @@ var effectsEffectSize = $.effects.define( "size", function( options, done ) {
 
 
 /*!
- * jQuery UI Effects Scale 1.13.1
+ * jQuery UI Effects Scale 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -3715,7 +3715,7 @@ var effectsEffectScale = $.effects.define( "scale", function( options, done ) {
 
 
 /*!
- * jQuery UI Effects Puff 1.13.1
+ * jQuery UI Effects Puff 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -3741,7 +3741,7 @@ var effectsEffectPuff = $.effects.define( "puff", "hide", function( options, don
 
 
 /*!
- * jQuery UI Effects Pulsate 1.13.1
+ * jQuery UI Effects Pulsate 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -3790,7 +3790,7 @@ var effectsEffectPulsate = $.effects.define( "pulsate", "show", function( option
 
 
 /*!
- * jQuery UI Effects Shake 1.13.1
+ * jQuery UI Effects Shake 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -3849,7 +3849,7 @@ var effectsEffectShake = $.effects.define( "shake", function( options, done ) {
 
 
 /*!
- * jQuery UI Effects Slide 1.13.1
+ * jQuery UI Effects Slide 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -3910,7 +3910,7 @@ var effectsEffectSlide = $.effects.define( "slide", "show", function( options, d
 
 
 /*!
- * jQuery UI Effects Transfer 1.13.1
+ * jQuery UI Effects Transfer 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -3935,7 +3935,7 @@ var effectsEffectTransfer = effect;
 
 
 /*!
- * jQuery UI Focusable 1.13.1
+ * jQuery UI Focusable 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -4017,7 +4017,7 @@ var form = $.fn._form = function() {
 
 
 /*!
- * jQuery UI Form Reset Mixin 1.13.1
+ * jQuery UI Form Reset Mixin 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -4079,7 +4079,7 @@ var formResetMixin = $.ui.formResetMixin = {
 
 
 /*!
- * jQuery UI Support for jQuery core 1.8.x and newer 1.13.1
+ * jQuery UI Support for jQuery core 1.8.x and newer 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -4154,7 +4154,7 @@ if ( !$.fn.even || !$.fn.odd ) {
 
 ;
 /*!
- * jQuery UI Keycode 1.13.1
+ * jQuery UI Keycode 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -4189,7 +4189,7 @@ var keycode = $.ui.keyCode = {
 
 
 /*!
- * jQuery UI Labels 1.13.1
+ * jQuery UI Labels 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -4244,7 +4244,7 @@ var labels = $.fn.labels = function() {
 
 
 /*!
- * jQuery UI Scroll Parent 1.13.1
+ * jQuery UI Scroll Parent 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -4278,7 +4278,7 @@ var scrollParent = $.fn.scrollParent = function( includeHidden ) {
 
 
 /*!
- * jQuery UI Tabbable 1.13.1
+ * jQuery UI Tabbable 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -4302,7 +4302,7 @@ var tabbable = $.extend( $.expr.pseudos, {
 
 
 /*!
- * jQuery UI Unique ID 1.13.1
+ * jQuery UI Unique ID 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -4340,7 +4340,7 @@ var uniqueId = $.fn.extend( {
 
 
 /*!
- * jQuery UI Accordion 1.13.1
+ * jQuery UI Accordion 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -4361,7 +4361,7 @@ var uniqueId = $.fn.extend( {
 
 
 var widgetsAccordion = $.widget( "ui.accordion", {
-	version: "1.13.1",
+	version: "1.13.2",
 	options: {
 		active: 0,
 		animate: {},
@@ -4972,7 +4972,7 @@ var safeActiveElement = $.ui.safeActiveElement = function( document ) {
 
 
 /*!
- * jQuery UI Menu 1.13.1
+ * jQuery UI Menu 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -4991,7 +4991,7 @@ var safeActiveElement = $.ui.safeActiveElement = function( document ) {
 
 
 var widgetsMenu = $.widget( "ui.menu", {
-	version: "1.13.1",
+	version: "1.13.2",
 	defaultElement: "<ul>",
 	delay: 300,
 	options: {
@@ -5663,7 +5663,7 @@ var widgetsMenu = $.widget( "ui.menu", {
 
 
 /*!
- * jQuery UI Autocomplete 1.13.1
+ * jQuery UI Autocomplete 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -5682,7 +5682,7 @@ var widgetsMenu = $.widget( "ui.menu", {
 
 
 $.widget( "ui.autocomplete", {
-	version: "1.13.1",
+	version: "1.13.2",
 	defaultElement: "<input>",
 	options: {
 		appendTo: null,
@@ -6319,7 +6319,7 @@ var widgetsAutocomplete = $.ui.autocomplete;
 
 
 /*!
- * jQuery UI Controlgroup 1.13.1
+ * jQuery UI Controlgroup 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -6340,7 +6340,7 @@ var widgetsAutocomplete = $.ui.autocomplete;
 var controlgroupCornerRegex = /ui-corner-([a-z]){2,6}/g;
 
 var widgetsControlgroup = $.widget( "ui.controlgroup", {
-	version: "1.13.1",
+	version: "1.13.2",
 	defaultElement: "<div>",
 	options: {
 		direction: "horizontal",
@@ -6604,7 +6604,7 @@ var widgetsControlgroup = $.widget( "ui.controlgroup", {
 } );
 
 /*!
- * jQuery UI Checkboxradio 1.13.1
+ * jQuery UI Checkboxradio 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -6624,7 +6624,7 @@ var widgetsControlgroup = $.widget( "ui.controlgroup", {
 
 
 $.widget( "ui.checkboxradio", [ $.ui.formResetMixin, {
-	version: "1.13.1",
+	version: "1.13.2",
 	options: {
 		disabled: null,
 		label: null,
@@ -6636,8 +6636,7 @@ $.widget( "ui.checkboxradio", [ $.ui.formResetMixin, {
 	},
 
 	_getCreateOptions: function() {
-		var disabled, labels;
-		var that = this;
+		var disabled, labels, labelContents;
 		var options = this._super() || {};
 
 		// We read the type here, because it makes more sense to throw a element type error first,
@@ -6657,12 +6656,18 @@ $.widget( "ui.checkboxradio", [ $.ui.formResetMixin, {
 
 		// We need to get the label text but this may also need to make sure it does not contain the
 		// input itself.
-		this.label.contents().not( this.element[ 0 ] ).each( function() {
+		// The label contents could be text, html, or a mix. We wrap all elements
+		// and read the wrapper's `innerHTML` to get a string representation of
+		// the label, without the input as part of it.
+		labelContents = this.label.contents().not( this.element[ 0 ] );
 
-			// The label contents could be text, html, or a mix. We concat each element to get a
-			// string representation of the label, without the input as part of it.
-			that.originalLabel += this.nodeType === 3 ? $( this ).text() : this.outerHTML;
-		} );
+		if ( labelContents.length ) {
+			this.originalLabel += labelContents
+				.clone()
+				.wrapAll( "<div></div>" )
+				.parent()
+				.html();
+		}
 
 		// Set the label option if we found label text
 		if ( this.originalLabel ) {
@@ -6870,7 +6875,7 @@ var widgetsCheckboxradio = $.ui.checkboxradio;
 
 
 /*!
- * jQuery UI Button 1.13.1
+ * jQuery UI Button 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -6889,7 +6894,7 @@ var widgetsCheckboxradio = $.ui.checkboxradio;
 
 
 $.widget( "ui.button", {
-	version: "1.13.1",
+	version: "1.13.2",
 	defaultElement: "<button>",
 	options: {
 		classes: {
@@ -7296,7 +7301,7 @@ var widgetsButton = $.ui.button;
 
 /* eslint-disable max-len, camelcase */
 /*!
- * jQuery UI Datepicker 1.13.1
+ * jQuery UI Datepicker 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -7314,7 +7319,7 @@ var widgetsButton = $.ui.button;
 //>>css.theme: ../../themes/base/theme.css
 
 
-$.extend( $.ui, { datepicker: { version: "1.13.1" } } );
+$.extend( $.ui, { datepicker: { version: "1.13.2" } } );
 
 var datepicker_instActive;
 
@@ -9511,7 +9516,7 @@ $.fn.datepicker = function( options ) {
 $.datepicker = new Datepicker(); // singleton instance
 $.datepicker.initialized = false;
 $.datepicker.uuid = new Date().getTime();
-$.datepicker.version = "1.13.1";
+$.datepicker.version = "1.13.2";
 
 var widgetsDatepicker = $.datepicker;
 
@@ -9521,7 +9526,7 @@ var widgetsDatepicker = $.datepicker;
 var ie = $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );
 
 /*!
- * jQuery UI Mouse 1.13.1
+ * jQuery UI Mouse 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -9541,7 +9546,7 @@ $( document ).on( "mouseup", function() {
 } );
 
 var widgetsMouse = $.widget( "ui.mouse", {
-	version: "1.13.1",
+	version: "1.13.2",
 	options: {
 		cancel: "input, textarea, button, select, option",
 		distance: 1,
@@ -9783,7 +9788,7 @@ var safeBlur = $.ui.safeBlur = function( element ) {
 
 
 /*!
- * jQuery UI Draggable 1.13.1
+ * jQuery UI Draggable 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -9800,7 +9805,7 @@ var safeBlur = $.ui.safeBlur = function( element ) {
 
 
 $.widget( "ui.draggable", $.ui.mouse, {
-	version: "1.13.1",
+	version: "1.13.2",
 	widgetEventPrefix: "drag",
 	options: {
 		addClasses: true,
@@ -11018,7 +11023,7 @@ var widgetsDraggable = $.ui.draggable;
 
 
 /*!
- * jQuery UI Resizable 1.13.1
+ * jQuery UI Resizable 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -11037,7 +11042,7 @@ var widgetsDraggable = $.ui.draggable;
 
 
 $.widget( "ui.resizable", $.ui.mouse, {
-	version: "1.13.1",
+	version: "1.13.2",
 	widgetEventPrefix: "resize",
 	options: {
 		alsoResize: false,
@@ -12216,7 +12221,7 @@ var widgetsResizable = $.ui.resizable;
 
 
 /*!
- * jQuery UI Dialog 1.13.1
+ * jQuery UI Dialog 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -12235,7 +12240,7 @@ var widgetsResizable = $.ui.resizable;
 
 
 $.widget( "ui.dialog", {
-	version: "1.13.1",
+	version: "1.13.2",
 	options: {
 		appendTo: "body",
 		autoOpen: true,
@@ -13141,7 +13146,7 @@ var widgetsDialog = $.ui.dialog;
 
 
 /*!
- * jQuery UI Droppable 1.13.1
+ * jQuery UI Droppable 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -13157,7 +13162,7 @@ var widgetsDialog = $.ui.dialog;
 
 
 $.widget( "ui.droppable", {
-	version: "1.13.1",
+	version: "1.13.2",
 	widgetEventPrefix: "drop",
 	options: {
 		accept: "*",
@@ -13624,7 +13629,7 @@ var widgetsDroppable = $.ui.droppable;
 
 
 /*!
- * jQuery UI Progressbar 1.13.1
+ * jQuery UI Progressbar 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -13645,7 +13650,7 @@ var widgetsDroppable = $.ui.droppable;
 
 
 var widgetsProgressbar = $.widget( "ui.progressbar", {
-	version: "1.13.1",
+	version: "1.13.2",
 	options: {
 		classes: {
 			"ui-progressbar": "ui-corner-all",
@@ -13787,7 +13792,7 @@ var widgetsProgressbar = $.widget( "ui.progressbar", {
 
 
 /*!
- * jQuery UI Selectable 1.13.1
+ * jQuery UI Selectable 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -13804,7 +13809,7 @@ var widgetsProgressbar = $.widget( "ui.progressbar", {
 
 
 var widgetsSelectable = $.widget( "ui.selectable", $.ui.mouse, {
-	version: "1.13.1",
+	version: "1.13.2",
 	options: {
 		appendTo: "body",
 		autoRefresh: true,
@@ -14085,7 +14090,7 @@ var widgetsSelectable = $.widget( "ui.selectable", $.ui.mouse, {
 
 
 /*!
- * jQuery UI Selectmenu 1.13.1
+ * jQuery UI Selectmenu 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -14106,7 +14111,7 @@ var widgetsSelectable = $.widget( "ui.selectable", $.ui.mouse, {
 
 
 var widgetsSelectmenu = $.widget( "ui.selectmenu", [ $.ui.formResetMixin, {
-	version: "1.13.1",
+	version: "1.13.2",
 	defaultElement: "<select>",
 	options: {
 		appendTo: null,
@@ -14479,7 +14484,7 @@ var widgetsSelectmenu = $.widget( "ui.selectmenu", [ $.ui.formResetMixin, {
 		// Support: IE
 		// Setting the text selection kills the button focus in IE, but
 		// restoring the focus doesn't kill the selection.
-		this.button.focus();
+		this.button.trigger( "focus" );
 	},
 
 	_documentClick: {
@@ -14754,7 +14759,7 @@ var widgetsSelectmenu = $.widget( "ui.selectmenu", [ $.ui.formResetMixin, {
 
 
 /*!
- * jQuery UI Slider 1.13.1
+ * jQuery UI Slider 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -14773,7 +14778,7 @@ var widgetsSelectmenu = $.widget( "ui.selectmenu", [ $.ui.formResetMixin, {
 
 
 var widgetsSlider = $.widget( "ui.slider", $.ui.mouse, {
-	version: "1.13.1",
+	version: "1.13.2",
 	widgetEventPrefix: "slide",
 
 	options: {
@@ -15489,7 +15494,7 @@ var widgetsSlider = $.widget( "ui.slider", $.ui.mouse, {
 
 
 /*!
- * jQuery UI Sortable 1.13.1
+ * jQuery UI Sortable 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -15506,7 +15511,7 @@ var widgetsSlider = $.widget( "ui.slider", $.ui.mouse, {
 
 
 var widgetsSortable = $.widget( "ui.sortable", $.ui.mouse, {
-	version: "1.13.1",
+	version: "1.13.2",
 	widgetEventPrefix: "sort",
 	ready: false,
 	options: {
@@ -17082,7 +17087,7 @@ var widgetsSortable = $.widget( "ui.sortable", $.ui.mouse, {
 
 
 /*!
- * jQuery UI Spinner 1.13.1
+ * jQuery UI Spinner 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -17112,7 +17117,7 @@ function spinnerModifier( fn ) {
 }
 
 $.widget( "ui.spinner", {
-	version: "1.13.1",
+	version: "1.13.2",
 	defaultElement: "<input>",
 	widgetEventPrefix: "spin",
 	options: {
@@ -17643,7 +17648,7 @@ var widgetsSpinner = $.ui.spinner;
 
 
 /*!
- * jQuery UI Tabs 1.13.1
+ * jQuery UI Tabs 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -17662,7 +17667,7 @@ var widgetsSpinner = $.ui.spinner;
 
 
 $.widget( "ui.tabs", {
-	version: "1.13.1",
+	version: "1.13.2",
 	delay: 300,
 	options: {
 		active: null,
@@ -18547,7 +18552,7 @@ var widgetsTabs = $.ui.tabs;
 
 
 /*!
- * jQuery UI Tooltip 1.13.1
+ * jQuery UI Tooltip 1.13.2
  * http://jqueryui.com
  *
  * Copyright jQuery Foundation and other contributors
@@ -18566,7 +18571,7 @@ var widgetsTabs = $.ui.tabs;
 
 
 $.widget( "ui.tooltip", {
-	version: "1.13.1",
+	version: "1.13.2",
 	options: {
 		classes: {
 			"ui-tooltip": "ui-corner-all ui-widget-shadow"

File diff suppressed because it is too large
+ 1 - 1
htdocs/includes/jquery/js/jquery-ui.min.js


+ 3 - 3
htdocs/includes/tecnickcom/tcpdf/tcpdf_barcodes_1d.php

@@ -1337,14 +1337,14 @@ class TCPDFBarcode {
 		// calculate check digit
 		$sum_a = 0;
 		for ($i = 1; $i < $data_len; $i+=2) {
-			$sum_a += $code[$i];
+			$sum_a += (int) $code[$i];
 		}
 		if ($len > 12) {
 			$sum_a *= 3;
 		}
 		$sum_b = 0;
 		for ($i = 0; $i < $data_len; $i+=2) {
-			$sum_b += ($code[$i]);
+			$sum_b += (int) ($code[$i]);
 		}
 		if ($len < 13) {
 			$sum_b *= 3;
@@ -2171,7 +2171,7 @@ class TCPDFBarcode {
 
 	/**
 	 * IMB - Intelligent Mail Barcode - Onecode - USPS-B-3200
-	 * 
+	 *
 	 * @param $code (string) pre-formatted IMB barcode (65 chars "FADT")
 	 * @return array barcode representation.
 	 * @protected

+ 4 - 4
htdocs/index.php

@@ -155,14 +155,14 @@ if (empty($conf->global->MAIN_DISABLE_GLOBAL_WORKBOARD)) {
 	require_once DOL_DOCUMENT_ROOT.'/core/class/workboardresponse.class.php';
 
 	// Number of actions to do (late)
-	if (isModEnabled('agenda') && empty($conf->global->MAIN_DISABLE_BLOCK_AGENDA) && $user->rights->agenda->myactions->read) {
+	if (isModEnabled('agenda') && empty($conf->global->MAIN_DISABLE_BLOCK_AGENDA) && $user->hasRight('agenda', 'myactions', 'read')) {
 		include_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
 		$board = new ActionComm($db);
 		$dashboardlines[$board->element] = $board->load_board($user);
 	}
 
 	// Number of project opened
-	if (!empty($conf->project->enabled) && empty($conf->global->MAIN_DISABLE_BLOCK_PROJECT) && $user->hasRight('projet', 'lire')) {
+	if (isModEnabled('project') && empty($conf->global->MAIN_DISABLE_BLOCK_PROJECT) && $user->hasRight('projet', 'lire')) {
 		include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
 		$board = new Project($db);
 		$dashboardlines[$board->element] = $board->load_board($user);
@@ -202,7 +202,7 @@ if (empty($conf->global->MAIN_DISABLE_GLOBAL_WORKBOARD)) {
 	}
 
 	// Number of suppliers orders a deal
-	if (isModEnabled('supplier_order')  && empty($conf->global->MAIN_DISABLE_BLOCK_SUPPLIER) && $user->rights->fournisseur->commande->lire) {
+	if (isModEnabled('supplier_order')  && empty($conf->global->MAIN_DISABLE_BLOCK_SUPPLIER) && $user->hasRight('fournisseur', 'commande', 'lire')) {
 		include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
 		$board = new CommandeFournisseur($db);
 		$dashboardlines[$board->element.'_opened'] = $board->load_board($user, "opened");
@@ -235,7 +235,7 @@ if (empty($conf->global->MAIN_DISABLE_GLOBAL_WORKBOARD)) {
 	}
 
 	// Number of supplier invoices (paid)
-	if (isModEnabled('supplier_invoice')  && empty($conf->global->MAIN_DISABLE_BLOCK_SUPPLIER) && !empty($user->rights->fournisseur->facture->lire)) {
+	if (isModEnabled('supplier_invoice') && empty($conf->global->MAIN_DISABLE_BLOCK_SUPPLIER) && $user->hasRight('fournisseur', 'facture', 'lire')) {
 		include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
 		$board = new FactureFournisseur($db);
 		$dashboardlines[$board->element] = $board->load_board($user);

BIN
htdocs/install/doctemplates/websites/website_template-style01.png


BIN
htdocs/install/doctemplates/websites/website_template-style01.zip


BIN
htdocs/install/doctemplates/websites/website_template-style02.zip


+ 5 - 0
htdocs/install/mysql/migration/16.0.0-17.0.0.sql

@@ -77,3 +77,8 @@ ALTER TABLE llx_recruitment_recruitmentcandidature ADD email_date datetime after
 ALTER TABLE llx_ticket ADD email_date datetime after email_msgid;
 
 INSERT INTO llx_const (name, entity, value, type, visible) VALUES ('MAIN_SECURITY_MAX_IMG_IN_HTML_CONTENT', 1, 1000, 'int', 0);
+
+ALTER TABLE llx_adherent ADD COLUMN default_lang VARCHAR(6) DEFAULT NULL AFTER datefin;
+
+-- Make sell-by or eat-by date mandatory
+ALTER TABLE llx_product ADD COLUMN sell_or_eat_by_mandatory tinyint DEFAULT 0 NOT NULL AFTER tobatch;

+ 1 - 0
htdocs/install/mysql/tables/llx_adherent.sql

@@ -71,6 +71,7 @@ create table llx_adherent
   statut           smallint NOT NULL DEFAULT 0,
   public           smallint NOT NULL DEFAULT 0,   -- certain champ de la fiche sont ils public ou pas ?
   datefin          datetime,                      -- end date of validity of the contribution / date de fin de validite de la cotisation
+  default_lang     varchar(6) DEFAULT NULL,
   note_private     text DEFAULT NULL,
   note_public      text DEFAULT NULL,
   model_pdf		     varchar(255),

+ 1 - 0
htdocs/install/mysql/tables/llx_product.sql

@@ -59,6 +59,7 @@ create table llx_product
   tobuy                         tinyint      DEFAULT 1,             -- Product you buy
   onportal                      tinyint      DEFAULT 0,	            -- If it is a product you sell and you want to sell it on portal (module website must be on)
   tobatch                       tinyint      DEFAULT 0 NOT NULL,    -- Is it a product that need a batch management (eat-by or lot management)
+  sell_or_eat_by_mandatory      tinyint      DEFAULT 0 NOT NULL,    -- Make sell-by or eat-by date mandatory
   batch_mask			        varchar(32)  DEFAULT NULL,          -- If the product has batch feature, you may want to use a batch mask per product
   fk_product_type               integer      DEFAULT 0,             -- Type of product: 0 for regular product, 1 for service, 9 for other (used by external module)
   duration                      varchar(6),

+ 15 - 6
htdocs/knowledgemanagement/class/api_knowledgemanagement.class.php

@@ -125,18 +125,19 @@ class KnowledgeManagement extends DolibarrApi
 	 *
 	 * Get a list of knowledgerecords
 	 *
-	 * @param string	       $sortfield	        Sort field
-	 * @param string	       $sortorder	        Sort order
-	 * @param int		       $limit		        Limit for list
-	 * @param int		       $page		        Page number
-	 * @param string           $sqlfilters          Other criteria to filter answers separated by a comma. Syntax example "(t.ref:like:'SO-%') and (t.date_creation:<:'20160101')"
+	 * @param string	       	$sortfield	        Sort field
+	 * @param string	       	$sortorder	        Sort order
+	 * @param int		       	$limit		        Limit for list
+	 * @param int		       	$page		        Page number
+	 * @param int				$category   		Use this param to filter list by category
+	 * @param string           	$sqlfilters          Other criteria to filter answers separated by a comma. Syntax example "(t.ref:like:'SO-%') and (t.date_creation:<:'20160101')"
 	 * @return  array                               Array of order objects
 	 *
 	 * @throws RestException
 	 *
 	 * @url	GET /knowledgerecords/
 	 */
-	public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $sqlfilters = '')
+	public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $category = 0, $sqlfilters = '')
 	{
 		global $db, $conf;
 
@@ -166,6 +167,9 @@ class KnowledgeManagement extends DolibarrApi
 		if ($restrictonsocid && (!DolibarrApiAccess::$user->rights->societe->client->voir && !$socid) || $search_sale > 0) {
 			$sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; // We need this table joined to the select in order to filter by sale
 		}
+		if ($category > 0) {
+			$sql .= ", ".$this->db->prefix()."categorie_knowledgemanagement as c";
+		}
 		$sql .= " WHERE 1 = 1";
 
 		// Example of use $mode
@@ -188,6 +192,11 @@ class KnowledgeManagement extends DolibarrApi
 		if ($restrictonsocid && $search_sale > 0) {
 			$sql .= " AND sc.fk_user = ".((int) $search_sale);
 		}
+		// Select products of given category
+		if ($category > 0) {
+			$sql .= " AND c.fk_categorie = ".((int) $category);
+			$sql .= " AND c.fk_knowledgemanagement = t.rowid";
+		}
 		if ($sqlfilters) {
 			$errormessage = '';
 			if (!DolibarrApi::_checkFilters($sqlfilters, $errormessage)) {

+ 18 - 0
htdocs/knowledgemanagement/class/knowledgerecord.class.php

@@ -468,6 +468,24 @@ class KnowledgeRecord extends CommonObject
 			$this->error .= $this->db->lasterror();
 			$errorflag = -1;
 		}
+
+		// Delete all child tables
+		if (!$error) {
+			$elements = array('categorie_knowledgemanagement');
+			foreach ($elements as $table) {
+				if (!$error) {
+					$sql = "DELETE FROM ".MAIN_DB_PREFIX.$table;
+					$sql .= " WHERE fk_knowledgemanagement = ".(int) $this->id;
+
+					$result = $this->db->query($sql);
+					if (!$result) {
+						$error++;
+						$this->errors[] = $this->db->lasterror();
+					}
+				}
+			}
+		}
+
 		return $this->deleteCommon($user, $notrigger);
 		//return $this->deleteCommon($user, $notrigger, 1);
 	}

+ 7 - 1
htdocs/langs/en_US/admin.lang

@@ -2287,6 +2287,12 @@ DoesNotWorkWithAllThemes=Will not work with all themes
 NoName=No name
 ShowAdvancedOptions= Show advanced options
 HideAdvancedoptions= Hide advanced options
+CIDLookupURL=The module brings an URL that can be used by an external tool to get the name of a thirdparty or contact from its phone number. URL to use is:
+OauthNotAvailableForAllAndHadToBeCreatedBefore=OAUTH2 authentication is not available for all hosts, and a token with the right permissions must have been created upstream with the OAUTH module
+MAIN_MAIL_SMTPS_OAUTH_SERVICE=OAUTH2 authentication service
+DontForgetCreateTokenOauthMod=A token with the right permissions must have been created upstream with the OAUTH module
+MAIN_MAIL_SMTPS_AUTH_TYPE=Authentification method
+UsePassword=Use a password
+UseOauth=Use a OAUTH token
 Images=Images
 MaxNumberOfImagesInGetPost=Max number of images allowed in GETPOST check
-CIDLookupURL=The module brings an URL that can be used by an external tool to get the name of a thirdparty or contact from its phone number. URL to use is: 

+ 1 - 0
htdocs/langs/en_US/errors.lang

@@ -290,6 +290,7 @@ ErrorDeleteLineNotAllowedByObjectStatus=Delete line is not allowed by current ob
 ErrorAjaxRequestFailed=Request failed
 ErrorThirpdartyOrMemberidIsMandatory=Third party or Member of partnership is mandatory
 ErrorFailedToWriteInTempDirectory=Failed to write in temp directory
+ErrorQuantityIsLimitedTo=Quantity is limited to %s
 
 # Warnings
 WarningParamUploadMaxFileSizeHigherThanPostMaxSize=Your PHP parameter upload_max_filesize (%s) is higher than PHP parameter post_max_size (%s). This is not a consistent setup.

+ 5 - 2
htdocs/langs/en_US/oauth.lang

@@ -9,8 +9,9 @@ HasAccessToken=A token was generated and saved into local database
 NewTokenStored=Token received and saved
 ToCheckDeleteTokenOnProvider=Click here to check/delete authorization saved by %s OAuth provider
 TokenDeleted=Token deleted
+GetAccess=Click here to get a token
 RequestAccess=Click here to request/renew access and receive a new token
-DeleteAccess=Click here to delete token
+DeleteAccess=Click here to delete the token
 UseTheFollowingUrlAsRedirectURI=Use the following URL as the Redirect URI when creating your credentials with your OAuth provider:
 ListOfSupportedOauthProviders=Add your OAuth2 token providers. Then, go on your OAuth provider admin page to create/get an OAuth ID and Secret and save them here. Once done, switch on the other tab to generate your token.
 OAuthSetupForLogin=Page to manage (generate/delete) OAuth tokens
@@ -33,4 +34,6 @@ OAUTH_STRIPE_LIVE_NAME=OAuth Stripe Live
 OAUTH_ID=OAuth ID
 OAUTH_SECRET=OAuth secret
 OAuthProviderAdded=OAuth provider added
-AOAuthEntryForThisProviderAndLabelAlreadyHasAKey=An OAuth entry for this provider and label already exists
+AOAuthEntryForThisProviderAndLabelAlreadyHasAKey=An OAuth entry for this provider and label already exists
+URLOfServiceForAuthorization=URL provided by OAuth service for authentication
+Scopes=Scopes

+ 3 - 0
htdocs/langs/en_US/other.lang

@@ -328,3 +328,6 @@ FailedToGetFile=Failed to get files %s
 ErrorFTPNodisconnect=Error to disconnect FTP/SFTP server
 FileWasUpload=File <b>%s</b> was upload
 FTPFailedToUploadFile=Failed to upload file <b>%s</b>.
+AddFolder=Create folder
+FileWasCreateFolder=Folder <b>%s</b> was create
+FTPFailedToCreateFolder=Failed to create folder <b>%s</b>.

+ 1 - 0
htdocs/langs/en_US/website.lang

@@ -150,3 +150,4 @@ ErrorFaviconSize=Favicon must be sized 16x16, 32x32 or 64x64
 FaviconTooltip=Upload an image which needs to be a png (16x16, 32x32 or 64x64)
 NextContainer=Next page/container
 PreviousContainer=Previous page/container
+WebsiteMustBeDisabled=The website must have the status "Disabled"

+ 7 - 0
htdocs/langs/fr_FR/admin.lang

@@ -2286,3 +2286,10 @@ NoName=Sans nom
 ShowAdvancedOptions= Show advanced options
 HideAdvancedoptions= Hide advanced options
 CIDLookupURL=The module brings an URL that can be used by an external tool to get the name of a thirdparty or contact from its phone number. URL to use is:
+DoesNotWorkWithAllThemes=Ne fonctionne pas avec tous les thèmes
+OauthNotAvailableForAllAndHadToBeCreatedBefore=L'authentification OAUTH2 n'est pas disponible pour tous les hôtes, de plus, un jeton avec les bonnes permissions doit avoir été créé en amont avec le module OAUTH
+MAIN_MAIL_SMTPS_OAUTH_SERVICE=Service d'authentification OAUTH2
+DontForgetCreateTokenOauthMod=Un jeton avec les bonnes permissions doit avoir été créé en amont avec le module OAUTH
+MAIN_MAIL_SMTPS_AUTH_TYPE=Méthode d'authentication
+UsePassword=Utiliser un mot de passe
+UseOauth=Utiliser un jeton d'authentification OAUTH

+ 3 - 2
htdocs/main.inc.php

@@ -197,7 +197,7 @@ function testSqlAndScriptInject($val, $type)
 /**
  * Return true if security check on parameters are OK, false otherwise.
  *
- * @param		string			$var		Variable name
+ * @param		string|array	$var		Variable name
  * @param		string			$type		1=GET, 0=POST, 2=PHP_SELF
  * @return		boolean|null				true if there is no injection. Stop code if injection found.
  */
@@ -536,7 +536,7 @@ if ((!defined('NOCSRFCHECK') && empty($dolibarr_nocsrfcheck) && getDolGlobalInt(
 	}
 
 	// Check a token is provided for all cases that need a mandatory token
-	// (all POST actions + all login, actions and mass actions on pages with CSRFCHECK_WITH_TOKEN set + all sensitive GET actions)
+	// (all POST actions + all sensitive GET actions + all mass actions + all login/actions/logout on pages with CSRFCHECK_WITH_TOKEN set)
 	if (
 		$_SERVER['REQUEST_METHOD'] == 'POST' ||
 		$sensitiveget ||
@@ -1458,6 +1458,7 @@ function top_httphead($contenttype = 'text/html', $forcenocache = 0)
 	if ($forcenocache) {
 		header("Cache-Control: no-cache, no-store, must-revalidate, max-age=0");
 	}
+	header("anti-csrf-token: ".newToken());
 }
 
 /**

+ 4 - 4
htdocs/mrp/class/mo.class.php

@@ -101,7 +101,7 @@ class Mo extends CommonObject
 		'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-2, 'position'=>1, 'notnull'=>1, 'index'=>1, 'comment'=>"Id",),
 		'entity' => array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'position'=>5, 'notnull'=>1, 'default'=>'1', 'index'=>1),
 		'ref' => array('type'=>'varchar(128)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>4, 'position'=>10, 'notnull'=>1, 'default'=>'(PROV)', 'index'=>1, 'searchall'=>1, 'comment'=>"Reference of object", 'showoncombobox'=>'1', 'noteditable'=>1),
-		'fk_bom' => array('type'=>'integer:Bom:bom/class/bom.class.php:0:t.status=1', 'filter'=>'active=1', 'label'=>'BOM', 'enabled'=>'$conf->bom->enabled', 'visible'=>1, 'position'=>33, 'notnull'=>-1, 'index'=>1, 'comment'=>"Original BOM", 'css'=>'minwidth100 maxwidth300', 'csslist'=>'nowraponall'),
+		'fk_bom' => array('type'=>'integer:Bom:bom/class/bom.class.php:0:t.status=1', 'filter'=>'active=1', 'label'=>'BOM', 'enabled'=>'$conf->bom->enabled', 'visible'=>1, 'position'=>33, 'notnull'=>-1, 'index'=>1, 'comment'=>"Original BOM", 'css'=>'minwidth100 maxwidth300', 'csslist'=>'nowraponall', 'picto'=>'bom'),
 		'mrptype' => array('type'=>'integer', 'label'=>'Type', 'enabled'=>1, 'visible'=>1, 'position'=>34, 'notnull'=>1, 'default'=>'0', 'arrayofkeyval'=>array(0=>'Manufacturing', 1=>'Disassemble'), 'css'=>'minwidth150', 'csslist'=>'minwidth150 center'),
 		'fk_product' => array('type'=>'integer:Product:product/class/product.class.php:0', 'label'=>'Product', 'enabled'=>'$conf->product->enabled', 'visible'=>1, 'position'=>35, 'notnull'=>1, 'index'=>1, 'comment'=>"Product to produce", 'css'=>'maxwidth300', 'csslist'=>'tdoverflowmax100', 'picto'=>'product'),
 		'qty' => array('type'=>'real', 'label'=>'QtyToProduce', 'enabled'=>1, 'visible'=>1, 'position'=>40, 'notnull'=>1, 'comment'=>"Qty to produce", 'css'=>'width75', 'default'=>1, 'isameasure'=>1),
@@ -1375,13 +1375,13 @@ class Mo extends CommonObject
 
 		print '<tr class="liste_titre">';
 		// Product or sub-bom
-		print '<td class="linecoldescription">'.$langs->trans('Description');
+		print '<td class="linecoldescription">'.$langs->trans('Ref');
 		if (!empty($conf->global->BOM_SUB_BOM)) {
 			print ' &nbsp; <a id="show_all" href="#">'.img_picto('', 'folder-open', 'class="paddingright"').$langs->trans("ExpandAll").'</a>&nbsp;&nbsp;';
 			print '<a id="hide_all" href="#">'.img_picto('', 'folder', 'class="paddingright"').$langs->trans("UndoExpandAll").'</a>&nbsp;';
 		}
 		print '</td>';
-		print '<td>'.$langs->trans('Ref').'</td>';
+		// Qty
 		print '<td class="right">'.$langs->trans('Qty');
 		if ($this->bom->bomtype == 0) {
 			print ' <span class="opacitymedium">('.$langs->trans("ForAQuantityOf", $this->bom->qty).')</span>';
@@ -1395,7 +1395,7 @@ class Mo extends CommonObject
 		print '<td class="center">'.$langs->trans('DisableStockChange').'</td>';
 		print '<td class="center">'.$langs->trans('MoChildGenerate').'</td>';
 		//print '<td class="center">'.$form->showCheckAddButtons('checkforselect', 1).'</td>';
-		//      print '<td class="center"></td>';
+		//print '<td class="center"></td>';
 		print '</tr>';
 		$i = 0;
 

+ 39 - 30
htdocs/mrp/mo_production.php

@@ -257,6 +257,7 @@ if (empty($reshook)) {
 
 		// Process line to produce
 		$pos = 0;
+
 		foreach ($object->lines as $line) {
 			if ($line->role == 'toproduce') {
 				$tmpproduct = new Product($db);
@@ -430,6 +431,8 @@ $tmpstockmovement = new MouvementStock($db);
 $help_url = 'EN:Module_Manufacturing_Orders|FR:Module_Ordres_de_Fabrication';
 llxHeader('', $langs->trans('Mo'), $help_url, '', 0, 0, array('/mrp/js/lib_dispatch.js.php'));
 
+$newToken = newToken();
+
 // Part to show record
 if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'create'))) {
 	$res = $object->fetch_thirdparty();
@@ -588,7 +591,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 			if ($object->status == $object::STATUS_DRAFT) {
 				if ($permissiontoadd) {
 					if (empty($object->table_element_line) || (is_array($object->lines) && count($object->lines) > 0)) {
-						print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&action=validate">'.$langs->trans("Validate").'</a>';
+						print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&action=validate&token='.$newToken.'">'.$langs->trans("Validate").'</a>';
 					} else {
 						$langs->load("errors");
 						print '<a class="butActionRefused" href="" title="'.$langs->trans("ErrorAddAtLeastOneLineFirst").'">'.$langs->trans("Validate").'</a>';
@@ -599,7 +602,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 			// Consume or produce
 			if ($object->status == Mo::STATUS_VALIDATED || $object->status == Mo::STATUS_INPROGRESS) {
 				if ($permissiontoproduce) {
-					print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=consumeorproduce">'.$langs->trans('ConsumeOrProduce').'</a>';
+					print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=consumeorproduce&token='.$newToken.'">'.$langs->trans('ConsumeOrProduce').'</a>';
 				} else {
 					print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans('ConsumeOrProduce').'</a>';
 				}
@@ -610,7 +613,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 			// ConsumeAndProduceAll
 			if ($object->status == Mo::STATUS_VALIDATED || $object->status == Mo::STATUS_INPROGRESS) {
 				if ($permissiontoproduce) {
-					print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=consumeandproduceall">'.$langs->trans('ConsumeAndProduceAll').'</a>';
+					print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=consumeandproduceall&token='.$newToken.'">'.$langs->trans('ConsumeAndProduceAll').'</a>';
 				} else {
 					print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans('ConsumeAndProduceAll').'</a>';
 				}
@@ -627,21 +630,21 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 						$nbProduced += $lineproduced['qty'];
 					}
 					if ($nbProduced > 0) {	// If production has started, we can close it
-						print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_produced&confirm=yes">'.$langs->trans("Close").'</a>'."\n";
+						print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_produced&confirm=yes&token='.$newToken.'">'.$langs->trans("Close").'</a>'."\n";
 					} else {
 						print '<a class="butActionRefused" href="#" title="'.$langs->trans("GoOnTabProductionToProduceFirst", $langs->transnoentitiesnoconv("Production")).'">'.$langs->trans("Close").'</a>'."\n";
 					}
 
-					print '<a class="butActionDelete" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_close&confirm=yes">'.$langs->trans("Cancel").'</a>'."\n";
+					print '<a class="butActionDelete" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_close&confirm=yes&token='.$newToken.'">'.$langs->trans("Cancel").'</a>'."\n";
 				}
 
 				if ($object->status == $object::STATUS_CANCELED) {
-					print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_reopen&confirm=yes">'.$langs->trans("Re-Open").'</a>'."\n";
+					print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_reopen&confirm=yes&token='.$newToken.'">'.$langs->trans("Re-Open").'</a>'."\n";
 				}
 
 				if ($object->status == $object::STATUS_PRODUCED) {
 					if ($permissiontoproduce) {
-						print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_reopen">'.$langs->trans('ReOpen').'</a>';
+						print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_reopen&token='.$newToken.'">'.$langs->trans('ReOpen').'</a>';
 					} else {
 						print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans('ReOpen').'</a>';
 					}
@@ -708,7 +711,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 		print '<div class="clearboth"></div>';
 
 		$url = $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=addconsumeline&token='.newToken();
-		$permissiontoaddaconsumeline = $object->status != $object::STATUS_PRODUCED && $object->status != $object::STATUS_CANCELED && $action != 'consumeorproduce' && $action != 'consumeandproduceall';
+		$permissiontoaddaconsumeline = $object->status != $object::STATUS_PRODUCED && $object->status != $object::STATUS_CANCELED;
 		$parameters = array('morecss'=>'reposition');
 
 		$newcardbutton = '';
@@ -996,6 +999,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 						$i = 1;
 						print '<!-- Enter line to consume -->'."\n";
 						print '<tr name="batch_'.$line->id.'_'.$i.'">';
+						// Ref
 						print '<td><span class="opacitymedium">'.$langs->trans("ToConsume").'</span></td>';
 						$preselected = (GETPOSTISSET('qty-'.$line->id.'-'.$i) ? GETPOST('qty-'.$line->id.'-'.$i) : max(0, $line->qty - $alreadyconsumed));
 						if ($action == 'consumeorproduce' && !GETPOSTISSET('qty-'.$line->id.'-'.$i)) {
@@ -1044,12 +1048,9 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 								$preselected = (GETPOSTISSET('batch-'.$line->id.'-'.$i) ? GETPOST('batch-'.$line->id.'-'.$i) : '');
 								print '<input type="text" class="width50" name="batch-'.$line->id.'-'.$i.'" value="'.$preselected.'" list="batch-'.$line->id.'-'.$i.'">';
 								print $formproduct->selectLotDataList('batch-'.$line->id.'-'.$i, 0, $line->fk_product, '', '');
-							}
-							print '</td>';
-							print '<td>';
-							if ($tmpproduct->status_batch) {
+
 								$type = 'batch';
-								print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.$line->id.', \''.$type.'\', \'qtymissingconsume\')"');
+								print ' '.img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.((int) $line->id).', \''.dol_escape_js($type).'\', \'qtymissingconsume\')"');
 							}
 							print '</td>';
 						}
@@ -1094,10 +1095,12 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 
 		$newcardbutton = '';
 		$url = $_SERVER["PHP_SELF"].'?id='.$object->id.'&action=addproduceline&token='.newToken();
-		$permissiontoaddaproductline = $object->status != $object::STATUS_PRODUCED && $object->status != $object::STATUS_CANCELED && $action != 'consumeorproduce' && $action != 'consumeandproduceall';
+		$permissiontoaddaproductline = $object->status != $object::STATUS_PRODUCED && $object->status != $object::STATUS_CANCELED;
 		$parameters = array('morecss'=>'reposition');
-		if ($nblinetoproduce == 0 || $object->mrptype == 1) {
-			$newcardbutton = dolGetButtonTitle($langs->trans('AddNewProduceLines'), '', 'fa fa-plus-circle size15x', $url, '', $permissiontoaddaproductline, $parameters);
+		if ($action != 'consumeorproduce' && $action != 'consumeandproduceall') {
+			if ($nblinetoproduce == 0 || $object->mrptype == 1) {
+				$newcardbutton = dolGetButtonTitle($langs->trans('AddNewProduceLines'), '', 'fa fa-plus-circle size15x', $url, '', $permissiontoaddaproductline, $parameters);
+			}
 		}
 
 		print load_fiche_titre($langs->trans('Production'), $newcardbutton, '', 0, '', '');
@@ -1196,12 +1199,15 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 					print '<td class="right">'.$line->qty.'</td>';
 					if ($permissiontoupdatecost) {
 						// Defined $manufacturingcost
-						$manufacturingcost = $bomcost;
-						if (empty($manufacturingcost)) {
-							$manufacturingcost = price2num($tmpproduct->cost_price, 'MU');
-						}
-						if (empty($manufacturingcost)) {
-							$manufacturingcost = price2num($tmpproduct->pmp, 'MU');
+						$manufacturingcost = 0;
+						if ($object->mrptype == 0) {	// If MO is a "Manufacture" type (and not "Disassemble"
+							$manufacturingcost = $bomcost;
+							if (empty($manufacturingcost)) {
+								$manufacturingcost = price2num($tmpproduct->cost_price, 'MU');
+							}
+							if (empty($manufacturingcost)) {
+								$manufacturingcost = price2num($tmpproduct->pmp, 'MU');
+							}
 						}
 
 						print '<td class="right nowraponall">';
@@ -1300,19 +1306,22 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 						print '<td class="right"><input type="text" class="width50 right" id="qtytoproduce-'.$line->id.'-'.$i.'" name="qtytoproduce-'.$line->id.'-'.$i.'" value="'.$preselected.'"></td>';
 						if ($permissiontoupdatecost) {
 							// Defined $manufacturingcost
-							$manufacturingcost = $bomcost;
-							if (empty($manufacturingcost)) {
-								$manufacturingcost = price2num($tmpproduct->cost_price, 'MU');
-							}
-							if (empty($manufacturingcost)) {
-								$manufacturingcost = price2num($tmpproduct->pmp, 'MU');
+							$manufacturingcost = 0;
+							if ($object->mrptype == 0) {	// If MO is a "Manufacture" type (and not "Disassemble"
+								$manufacturingcost = $bomcost;
+								if (empty($manufacturingcost)) {
+									$manufacturingcost = price2num($tmpproduct->cost_price, 'MU');
+								}
+								if (empty($manufacturingcost)) {
+									$manufacturingcost = price2num($tmpproduct->pmp, 'MU');
+								}
 							}
 
 							if ($tmpproduct->type == Product::TYPE_PRODUCT || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
-								$preselected = (GETPOSTISSET('pricetoproduce-'.$line->id.'-'.$i) ? GETPOST('pricetoproduce-'.$line->id.'-'.$i) : price($manufacturingcost));
+								$preselected = (GETPOSTISSET('pricetoproduce-'.$line->id.'-'.$i) ? GETPOST('pricetoproduce-'.$line->id.'-'.$i) : ($manufacturingcost ? price($manufacturingcost) : ''));
 								print '<td class="right"><input type="text" class="width50 right" name="pricetoproduce-'.$line->id.'-'.$i.'" value="'.$preselected.'"></td>';
 							} else {
-								print '<td><input type="hidden" class="width50 right" name="pricetoproduce-'.$line->id.'-'.$i.'" value="'.$manufacturingcost.'"></td>';
+								print '<td><input type="hidden" class="width50 right" name="pricetoproduce-'.$line->id.'-'.$i.'" value="'.($manufacturingcost ? $manufacturingcost : '').'"></td>';
 							}
 						}
 						print '<td></td>';

+ 8 - 6
htdocs/mrp/tpl/originproductline.tpl.php

@@ -44,6 +44,7 @@ $res = $tmpbom->fetch($line->fk_bom_child);
 <!-- BEGIN PHP TEMPLATE originproductline.tpl.php -->
 <?php
 print '<tr class="oddeven'.(empty($this->tpl['strike']) ? '' : ' strikefordisabled').'">';
+// Ref or label
 print '<td>';
 if ($res) {
 	print $tmpproduct->getNomUrl(1);
@@ -58,7 +59,7 @@ if ($res) {
 	print $this->tpl['label'];
 }
 print '</td>';
-//print '<td>'.$this->tpl['label'].'</td>';
+// Qty
 print '<td class="right">'.$this->tpl['qty'].(($this->tpl['efficiency'] > 0 && $this->tpl['efficiency'] < 1) ? ' / '.$form->textwithpicto($this->tpl['efficiency'], $langs->trans("ValueOfMeansLoss")).' = '.$qtytoconsumeforline : '').'</td>';
 print '<td class="center">'.(empty($this->tpl['stock']) ? 0 : price2num($this->tpl['stock'], 'MS'));
 if ($this->tpl['seuil_stock_alerte'] != '' && ($this->tpl['stock'] < $this->tpl['seuil_stock_alerte'])) {
@@ -78,18 +79,19 @@ $selected = 1;
 if (!empty($selectedLines) && !in_array($this->tpl['id'], $selectedLines)) {
 	$selected = 0;
 }
-print '<td class="center">';
-//print '<input id="cb'.$this->tpl['id'].'" class="flat checkforselect" type="checkbox" name="toselect[]" value="'.$this->tpl['id'].'"'.($selected?' checked="checked"':'').'>';
-print '</td>';
 
-if ($tmpbom->id) {
+if ($tmpbom->id > 0) {
 	print '<td class="center">';
 	print '<input type="checkbox" name="bomlineid[]" value="' . $line->id . '">';
 	print '</td>';
 } else {
-	print '<td class="center">&nbsp;</td>';
+	print '<td class="center"></td>';
 }
 
+//print '<td class="center">';
+//print '<input id="cb'.$this->tpl['id'].'" class="flat checkforselect" type="checkbox" name="toselect[]" value="'.$this->tpl['id'].'"'.($selected?' checked="checked"':'').'>';
+//print '</td>';
+
 print '</tr>'."\n";
 
 // Select of all the sub-BOM lines

+ 3 - 3
htdocs/product/card.php

@@ -16,7 +16,7 @@
  * Copyright (C) 2016-2022	Charlene Benke		 <charlene@patas-monkey.com>
  * Copyright (C) 2016		Meziane Sof		     <virtualsof@yahoo.fr>
  * Copyright (C) 2017		Josep Lluís Amador	 <joseplluis@lliuretic.cat>
- * Copyright (C) 2019-2021  Frédéric France      <frederic.france@netlogic.fr>
+ * Copyright (C) 2019-2022  Frédéric France      <frederic.france@netlogic.fr>
  * Copyright (C) 2019-2020  Thibault FOUCART     <support@ptibogxiv.net>
  * Copyright (C) 2020  		Pierre Ardoin     	 <mapiolca@me.com>
  *
@@ -1973,7 +1973,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 			print '<tr><td class="tdtop">'.$langs->trans("Description").'</td><td>';
 
 			// We use dolibarr_details as type of DolEditor here, because we must not accept images as description is included into PDF and not accepted by TCPDF.
-			$doleditor = new DolEditor('desc', $object->description, '', 160, 'dolibarr_details', '', false, true, getDolGlobalInt('FCKEDITOR_ENABLE_PRODUCTDESC'), ROWS_4, '90%');
+			$doleditor = new DolEditor('desc', GETPOSTISSET('desc') ? GETPOST('desc', 'restricthtml') : $object->description, '', 160, 'dolibarr_details', '', false, true, getDolGlobalInt('FCKEDITOR_ENABLE_PRODUCTDESC'), ROWS_4, '90%');
 			$doleditor->Create();
 
 			print "</td></tr>";
@@ -1983,7 +1983,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 			if (empty($conf->global->PRODUCT_DISABLE_PUBLIC_URL)) {
 				print '<tr><td>'.$langs->trans("PublicUrl").'</td><td>';
 				print img_picto('', 'globe', 'class="pictofixedwidth"');
-				print '<input type="text" name="url" class="quatrevingtpercent" value="'.$object->url.'">';
+				print '<input type="text" name="url" class="quatrevingtpercent" value="'.(GETPOSTISSET('url') ? GETPOST('url') : $object->url).'">';
 				print '</td></tr>';
 			}
 

+ 5 - 5
htdocs/product/class/api_products.class.php

@@ -253,7 +253,7 @@ class Products extends DolibarrApi
 				if (!$ids_only) {
 					$product_static = new Product($this->db);
 					if ($product_static->fetch($obj->rowid)) {
-						if ($includestockdata && DolibarrApiAccess::$user->rights->stock->lire) {
+						if (!empty($includestockdata) && DolibarrApiAccess::$user->rights->stock->lire) {
 							$product_static->load_stock();
 
 							if (is_array($product_static->stock_warehouse)) {
@@ -1644,10 +1644,10 @@ class Products extends DolibarrApi
 			$combinations[$key]->attributes = $prodc2vp->fetchByFkCombination((int) $combination->id);
 			$combinations[$key] = $this->_cleanObjectDatas($combinations[$key]);
 
-			if ($includestock==1 && DolibarrApiAccess::$user->rights->stock->lire) {
+			if (!empty($includestock) && DolibarrApiAccess::$user->rights->stock->lire) {
 				$productModel = new Product($this->db);
 				$productModel->fetch((int) $combination->fk_product_child);
-				$productModel->load_stock();
+				$productModel->load_stock($includestock);
 				$combinations[$key]->stock_warehouse = $this->_cleanObjectDatas($productModel)->stock_warehouse;
 			}
 		}
@@ -2040,8 +2040,8 @@ class Products extends DolibarrApi
 			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
 		}
 
-		if ($includestockdata && DolibarrApiAccess::$user->rights->stock->lire) {
-			$this->product->load_stock();
+		if (!empty($includestockdata) && DolibarrApiAccess::$user->rights->stock->lire) {
+			$this->product->load_stock($includestockdata);
 
 			if (is_array($this->product->stock_warehouse)) {
 				foreach ($this->product->stock_warehouse as $keytmp => $valtmp) {

+ 36 - 26
htdocs/product/class/product.class.php

@@ -1051,9 +1051,7 @@ class Product extends CommonObject
 
 		if ($result >= 0) {
 			if (empty($this->oldcopy)) {
-				$org = new self($this->db);
-				$org->fetch($this->id);
-				$this->oldcopy = $org;
+				$this->oldcopy = dol_clone($this);
 			}
 
 			// Test if batch management is activated on existing product
@@ -3038,12 +3036,13 @@ class Product extends CommonObject
 	/**
 	 *  Charge tableau des stats commande fournisseur pour le produit/service
 	 *
-	 * @param  int    $socid           Id societe pour filtrer sur une societe
-	 * @param  string $filtrestatut    Id des statuts pour filtrer sur des statuts
-	 * @param  int    $forVirtualStock Ignore rights filter for virtual stock calculation.
-	 * @return int                     Array of stats in $this->stats_commande_fournisseur, <0 if ko or >0 if ok
+	 * @param	int		$socid				Id societe pour filtrer sur une societe
+	 * @param	string	$filtrestatut		Id des statuts pour filtrer sur des statuts
+	 * @param	int		$forVirtualStock	Ignore rights filter for virtual stock calculation.
+	 * @param	int		$dateofvirtualstock	Date of virtual stock
+	 * @return	int							Array of stats in $this->stats_commande_fournisseur, <0 if ko or >0 if ok
 	 */
-	public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
+	public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
 	{
 		// phpcs:enable
 		global $conf, $user, $hookmanager, $action;
@@ -3069,6 +3068,9 @@ class Product extends CommonObject
 		if ($filtrestatut != '') {
 			$sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")"; // Peut valoir 0
 		}
+		if (!empty($dateofvirtualstock)) {
+			$sql .= " AND c.date_livraison <= '".$this->db->idate($dateofvirtualstock)."'";
+		}
 
 		$result = $this->db->query($sql);
 		if ($result) {
@@ -3181,12 +3183,13 @@ class Product extends CommonObject
 	/**
 	 *  Charge tableau des stats réception fournisseur pour le produit/service
 	 *
-	 * @param  int    $socid           Id societe pour filtrer sur une societe
-	 * @param  string $filtrestatut    Id statut pour filtrer sur un statut
-	 * @param  int    $forVirtualStock Ignore rights filter for virtual stock calculation.
+	 * @param  int    	$socid           Id societe pour filtrer sur une societe
+	 * @param  string 	$filtrestatut    Id statut pour filtrer sur un statut
+	 * @param  int    	$forVirtualStock Ignore rights filter for virtual stock calculation.
+	 * @param	int		$dateofvirtualstock	Date of virtual stock
 	 * @return int                     Array of stats in $this->stats_reception, <0 if ko or >0 if ok
 	 */
-	public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
+	public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
 	{
 		// phpcs:enable
 		global $conf, $user, $hookmanager, $action;
@@ -3212,6 +3215,9 @@ class Product extends CommonObject
 		if ($filtrestatut <> '') {
 			$sql .= " AND cf.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
 		}
+		if (!empty($dateofvirtualstock)) {
+			$sql .= " AND fd.datec <= '".$this->db->idate($dateofvirtualstock)."'";
+		}
 
 		$result = $this->db->query($sql);
 		if ($result) {
@@ -3238,12 +3244,13 @@ class Product extends CommonObject
 	/**
 	 *  Charge tableau des stats production pour le produit/service
 	 *
-	 * @param  int    $socid           Id societe pour filtrer sur une societe
-	 * @param  string $filtrestatut    Id statut pour filtrer sur un statut
-	 * @param  int    $forVirtualStock Ignore rights filter for virtual stock calculation.
+	 * @param  int    	$socid           Id societe pour filtrer sur une societe
+	 * @param  string 	$filtrestatut    Id statut pour filtrer sur un statut
+	 * @param  int    	$forVirtualStock Ignore rights filter for virtual stock calculation.
+	 * @param	int		$dateofvirtualstock	Date of virtual stock
 	 * @return integer                 Array of stats in $this->stats_mrptoproduce (nb=nb of order, qty=qty ordered), <0 if ko or >0 if ok
 	 */
-	public function load_stats_inproduction($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
+	public function load_stats_inproduction($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
 	{
 		// phpcs:enable
 		global $conf, $user, $hookmanager, $action;
@@ -3268,6 +3275,9 @@ class Product extends CommonObject
 		if ($filtrestatut <> '') {
 			$sql .= " AND m.status IN (".$this->db->sanitize($filtrestatut).")";
 		}
+		if (!empty($dateofvirtualstock)) {
+			$sql .= " AND m.date_valid <= '".$this->db->idate($dateofvirtualstock)."'"; // better date to code ? end of production ?
+		}
 		$sql .= " GROUP BY role";
 
 		$this->stats_mrptoconsume['customers'] = 0;
@@ -5037,7 +5047,7 @@ class Product extends CommonObject
 				$result .= (img_object(($notooltip ? '' : $label), 'service', ($notooltip ? 'class="paddingright"' : 'class="paddingright classfortooltip"'), 0, 0, $notooltip ? 0 : 1));
 			}
 		}
-		$result .= $newref;
+		$result .= dol_escape_htmltag($newref);
 		$result .= $linkend;
 		if ($withpicto != 2) {
 			$result .= (($add_label && $this->label) ? $sep.dol_trunc($this->label, ($add_label > 1 ? $add_label : 0)) : '');
@@ -5346,10 +5356,11 @@ class Product extends CommonObject
 	 * @param  	string 	$option 					'' = Load all stock info, also from closed and internal warehouses, 'nobatch', 'novirtual'
 	 * 												You can also filter on 'warehouseclosed', 'warehouseopen', 'warehouseinternal'
 	 * @param	int		$includedraftpoforvirtual	Include draft status of PO for virtual stock calculation
+	 * @param	int		$dateofvirtualstock			Date of virtual stock
 	 * @return 	int                  				< 0 if KO, > 0 if OK
 	 * @see    	load_virtual_stock(), loadBatchInfo()
 	 */
-	public function load_stock($option = '', $includedraftpoforvirtual = null)
+	public function load_stock($option = '', $includedraftpoforvirtual = null, $dateofvirtualstock = null)
 	{
 		// phpcs:enable
 		global $conf;
@@ -5407,7 +5418,7 @@ class Product extends CommonObject
 			$this->db->free($result);
 
 			if (!preg_match('/novirtual/', $option)) {
-				$this->load_virtual_stock($includedraftpoforvirtual); // This also load all arrays stats_xxx...
+				$this->load_virtual_stock($includedraftpoforvirtual, $dateofvirtualstock); // This also load all arrays stats_xxx...
 			}
 
 			return 1;
@@ -5424,10 +5435,11 @@ class Product extends CommonObject
 	 *  This function need a lot of load. If you use it on list, use a cache to execute it one for each product id.
 	 *
 	 * 	@param	int		$includedraftpoforvirtual	Include draft status and not yet approved Purchase Orders for virtual stock calculation
+	 *  @param	int		$dateofvirtualstock			Date of virtual stock
 	 *  @return int     							< 0 if KO, > 0 if OK
 	 *  @see	load_stock(), loadBatchInfo()
 	 */
-	public function load_virtual_stock($includedraftpoforvirtual = null)
+	public function load_virtual_stock($includedraftpoforvirtual = null, $dateofvirtualstock = null)
 	{
 		// phpcs:enable
 		global $conf, $hookmanager, $action;
@@ -5466,7 +5478,7 @@ class Product extends CommonObject
 			if (isset($includedraftpoforvirtual)) {
 				$filterStatus = '0,1,2,'.$filterStatus;	// 1,2 may have already been inside $filterStatus but it is better to have twice than missing $filterStatus does not include them
 			}
-			$result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1);
+			$result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1, $dateofvirtualstock);
 			if ($result < 0) {
 				dol_print_error($this->db, $this->error);
 			}
@@ -5478,7 +5490,7 @@ class Product extends CommonObject
 			if (isset($includedraftpoforvirtual)) {
 				$filterStatus = '0,'.$filterStatus;
 			}
-			$result = $this->load_stats_reception(0, $filterStatus, 1);
+			$result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock);
 			if ($result < 0) {
 				dol_print_error($this->db, $this->error);
 			}
@@ -5490,14 +5502,14 @@ class Product extends CommonObject
 			if (isset($includedraftpoforvirtual)) {
 				$filterStatus = '0,'.$filterStatus;
 			}
-			$result = $this->load_stats_reception(0, $filterStatus, 1); // Use same tables than when module reception is not used.
+			$result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock); // Use same tables than when module reception is not used.
 			if ($result < 0) {
 				dol_print_error($this->db, $this->error);
 			}
 			$stock_reception_fournisseur = $this->stats_reception['qty'];
 		}
 		if (!empty($conf->mrp->enabled)) {
-			$result = $this->load_stats_inproduction(0, '1,2', 1);
+			$result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock);
 			if ($result < 0) {
 				dol_print_error($this->db, $this->error);
 			}
@@ -6196,8 +6208,6 @@ class Product extends CommonObject
 	}
 }
 
-
-
 /**
  * Class to manage products or services.
  * Do not use 'Service' as class name since it is already used by APIs.

+ 5 - 2
htdocs/product/fournisseurs.php

@@ -464,6 +464,9 @@ if ($id > 0 || $ref) {
 			if (($action == 'add_price' || $action == 'update_price') && $usercancreate) {
 				$langs->load("suppliers");
 
+				print "<!-- form to add a supplier price -->\n";
+				print '<br>';
+
 				if ($rowid) {
 					$object->fetch_product_fournisseur_price($rowid, 1); //Ignore the math expression when getting the price
 					print load_fiche_titre($langs->trans("ChangeSupplierPrice"));
@@ -480,7 +483,7 @@ if ($id > 0 || $ref) {
 				print '<table class="border centpercent">';
 
 				// Supplier
-				print '<tr><td class="titlefieldcreate fieldrequired">'.$langs->trans("Supplier").'</td><td>';
+				print '<tr><td class="titlefield fieldrequired">'.$langs->trans("Supplier").'</td><td>';
 				if ($rowid) {
 					$supplier = new Fournisseur($db);
 					$supplier->fetch($socid);
@@ -636,7 +639,7 @@ if ($id > 0 || $ref) {
 						$currencycodetouse = $conf->currency;
 					}
 					print $form->selectMultiCurrency($currencycodetouse, "multicurrency_code", 1);
-					print ' &nbsp; '.$langs->trans("CurrencyRate").' ';
+					print ' &nbsp; &nbsp; '.$langs->trans("CurrencyRate").' ';
 					print '<input class="flat" name="multicurrency_tx" size="4" value="'.vatrate(GETPOST('multicurrency_tx') ? GETPOST('multicurrency_tx') : (isset($object->fourn_multicurrency_tx) ? $object->fourn_multicurrency_tx : '')).'">';
 					print '</td>';
 					print '</tr>';

+ 4 - 4
htdocs/product/note.php

@@ -86,21 +86,21 @@ $form = new Form($db);
 
 $help_url = '';
 if (GETPOST("type") == '0' || ($object->type == Product::TYPE_PRODUCT)) {
-	$help_url = 'EN:Module_Products|FR:Module_Produits|ES:M&oacute;dulo_Productos';
+	$help_url = 'EN:Module_Products|FR:Module_Produits|ES:M&oacute;dulo_Productos|DE:Modul_Produkte';
 }
 if (GETPOST("type") == '1' || ($object->type == Product::TYPE_SERVICE)) {
-	$help_url = 'EN:Module_Services_En|FR:Module_Services|ES:M&oacute;dulo_Servicios';
+	$help_url = 'EN:Module_Services_En|FR:Module_Services|ES:M&oacute;dulo_Servicios|DE:Modul_Leistungen';
 }
 
 $title = $langs->trans('ProductServiceCard');
 $shortlabel = dol_trunc($object->label, 16);
 if (GETPOST("type") == '0' || ($object->type == Product::TYPE_PRODUCT)) {
 	$title = $langs->trans('Product')." ".$shortlabel." - ".$langs->trans('Notes');
-	$help_url = 'EN:Module_Products|FR:Module_Produits|ES:M&oacute;dulo_Productos';
+	$help_url = 'EN:Module_Products|FR:Module_Produits|ES:M&oacute;dulo_Productos|DE:Modul_Produkte';
 }
 if (GETPOST("type") == '1' || ($object->type == Product::TYPE_SERVICE)) {
 	$title = $langs->trans('Service')." ".$shortlabel." - ".$langs->trans('Notes');
-	$help_url = 'EN:Module_Services_En|FR:Module_Services|ES:M&oacute;dulo_Servicios';
+	$help_url = 'EN:Module_Services_En|FR:Module_Services|ES:M&oacute;dulo_Servicios|DE:Modul_Leistungen';
 }
 
 llxHeader('', $title, $help_url);

+ 1 - 0
htdocs/product/popuprop.php

@@ -229,6 +229,7 @@ if ($mode && $mode != '-1') {
 			}
 		}
 
+		$tmpproduct->id = $prodid;
 		$tmpproduct->ref = $vals['ref'];
 		$tmpproduct->label = $vals['label'];
 		$tmpproduct->type = $vals['type'];

Some files were not shown because too many files changed in this diff