Browse Source

Merge branch 'develop' of github.com:Dolibarr/dolibarr into dev_BDD_19763

Florian HENRY 3 years ago
parent
commit
e0048c056a
45 changed files with 668 additions and 398 deletions
  1. 1 1
      SECURITY.md
  2. 0 21
      htdocs/adherents/partnership.php
  3. 1 0
      htdocs/admin/agenda.php
  4. 0 2
      htdocs/admin/const.php
  5. 1 1
      htdocs/admin/defaultvalues.php
  6. 14 8
      htdocs/admin/oauth.php
  7. 41 26
      htdocs/admin/oauthlogintokens.php
  8. 6 6
      htdocs/comm/mailing/card.php
  9. 3 3
      htdocs/compta/facture/class/facture.class.php
  10. 3 2
      htdocs/compta/tva/card.php
  11. 7 3
      htdocs/core/class/CSMSFile.class.php
  12. 13 9
      htdocs/core/class/html.formadmin.class.php
  13. 1 1
      htdocs/core/customreports.php
  14. 12 0
      htdocs/core/db/DoliDB.class.php
  15. 4 4
      htdocs/core/lib/functions.lib.php
  16. 8 5
      htdocs/core/lib/geturl.lib.php
  17. 5 5
      htdocs/core/lib/oauth.lib.php
  18. 0 1
      htdocs/core/modules/modPrinting.class.php
  19. 81 35
      htdocs/core/modules/oauth/google_oauthcallback.php
  20. 1 1
      htdocs/core/tpl/admin_extrafields_add.tpl.php
  21. 28 0
      htdocs/core/tpl/login.tpl.php
  22. 4 2
      htdocs/core/triggers/interface_50_modAgenda_ActionsAuto.class.php
  23. 0 1
      htdocs/eventorganization/conferenceorboothattendee_note.php
  24. 2 2
      htdocs/externalsite/admin/index.php
  25. 0 1
      htdocs/hrm/position.php
  26. 189 187
      htdocs/includes/OAuth/OAuth2/Service/Google.php
  27. 2 1
      htdocs/langs/en_US/other.lang
  28. 1 1
      htdocs/main.inc.php
  29. 1 1
      htdocs/modulebuilder/index.php
  30. 0 1
      htdocs/modulebuilder/template/scripts/mymodule.php
  31. 151 31
      htdocs/product/price.php
  32. 1 1
      htdocs/projet/tasks/note.php
  33. 0 2
      htdocs/public/onlinesign/newonlinesign.php
  34. 1 1
      htdocs/public/ticket/create_ticket.php
  35. 1 1
      htdocs/salaries/card.php
  36. 2 2
      htdocs/salaries/paiement_salary.php
  37. 20 5
      htdocs/theme/eldy/global.inc.php
  38. 1 0
      htdocs/theme/md/btn.inc.php
  39. 1 1
      htdocs/theme/md/main_menu_fa_icons.inc.php
  40. 14 6
      htdocs/theme/md/style.css.php
  41. 1 1
      htdocs/user/card.php
  42. 1 1
      htdocs/viewimage.php
  43. 21 15
      htdocs/website/index.php
  44. 19 0
      test/phpunit/CodingPhpTest.php
  45. 5 0
      test/phpunit/SecurityTest.php

+ 1 - 1
SECURITY.md

@@ -67,7 +67,7 @@ Scope is the web application (back office) and the APIs.
 * Remote code execution (RCE)
 * Local files access and manipulation (LFI, RFI, XXE, SSRF, XSPA)
 * Code injections (HTML, JS, SQL, PHP, ...)
-* Cross-Site Scripting (XSS), except from setup page of module "External web site" (allowing any content here, editable by admin user only, is accepted on purpose or into module "Web site" when permission to edit website content is allowed).
+* Cross-Site Scripting (XSS), except from setup page of module "External web site" (allowing any content here, editable by admin user only, is accepted on purpose) and except into module "Web site" when permission to edit website content is allowed (injecting any data in this case is allowed too).
 * Cross-Site Requests Forgery (CSRF) with real security impact (when using GET URLs, CSRF are qualified only for creating, updating or deleting data from pages restricted to admin users)
 * Open redirect
 * Broken authentication & session management

+ 0 - 21
htdocs/adherents/partnership.php

@@ -22,27 +22,6 @@
  *		\brief      Page to create/edit/view partnership
  */
 
-//if (! defined('NOREQUIREDB'))              define('NOREQUIREDB', '1');				// Do not create database handler $db
-//if (! defined('NOREQUIREUSER'))            define('NOREQUIREUSER', '1');				// Do not load object $user
-//if (! defined('NOREQUIRESOC'))             define('NOREQUIRESOC', '1');				// Do not load object $mysoc
-//if (! defined('NOREQUIRETRAN'))            define('NOREQUIRETRAN', '1');				// Do not load object $langs
-//if (! defined('NOSCANGETFORINJECTION'))    define('NOSCANGETFORINJECTION', '1');		// Do not check injection attack on GET parameters
-//if (! defined('NOSCANPOSTFORINJECTION'))   define('NOSCANPOSTFORINJECTION', '1');		// Do not check injection attack on POST parameters
-//if (! defined('NOCSRFCHECK'))              define('NOCSRFCHECK', '1');				// Do not check CSRF attack (test on referer + on token if option MAIN_SECURITY_CSRF_WITH_TOKEN is on).
-//if (! defined('NOTOKENRENEWAL'))           define('NOTOKENRENEWAL', '1');				// Do not roll the Anti CSRF token (used if MAIN_SECURITY_CSRF_WITH_TOKEN is on)
-//if (! defined('NOSTYLECHECK'))             define('NOSTYLECHECK', '1');				// Do not check style html tag into posted data
-//if (! defined('NOREQUIREMENU'))            define('NOREQUIREMENU', '1');				// If there is no need to load and show top and left menu
-//if (! defined('NOREQUIREHTML'))            define('NOREQUIREHTML', '1');				// If we don't need to load the html.form.class.php
-//if (! defined('NOREQUIREAJAX'))            define('NOREQUIREAJAX', '1');       	  	// Do not load ajax.lib.php library
-//if (! defined("NOLOGIN"))                  define("NOLOGIN", '1');					// If this page is public (can be called outside logged session). This include the NOIPCHECK too.
-//if (! defined('NOIPCHECK'))                define('NOIPCHECK', '1');					// Do not check IP defined into conf $dolibarr_main_restrict_ip
-//if (! defined("MAIN_LANG_DEFAULT"))        define('MAIN_LANG_DEFAULT', 'auto');					// Force lang to a particular value
-//if (! defined("MAIN_AUTHENTICATION_MODE")) define('MAIN_AUTHENTICATION_MODE', 'aloginmodule');	// Force authentication handler
-//if (! defined("NOREDIRECTBYMAINTOLOGIN"))  define('NOREDIRECTBYMAINTOLOGIN', 1);		// The main.inc.php does not make a redirect if not logged, instead show simple error message
-//if (! defined("FORCECSP"))                 define('FORCECSP', 'none');				// Disable all Content Security Policies
-//if (! defined('CSRFCHECK_WITH_TOKEN'))     define('CSRFCHECK_WITH_TOKEN', '1');		// Force use of CSRF protection with tokens even for GET
-//if (! defined('NOBROWSERNOTIF'))     		 define('NOBROWSERNOTIF', '1');				// Disable browser notification
-
 // Load Dolibarr environment
 require '../main.inc.php';
 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';

+ 1 - 0
htdocs/admin/agenda.php

@@ -192,6 +192,7 @@ if (!empty($triggers)) {
 			}
 
 			if ($search_event === '' || preg_match('/'.preg_quote($search_event, '/').'/i', $trigger['code'])) {
+				print '<!-- '.$trigger['position'].' -->';
 				print '<tr class="oddeven">';
 				print '<td>'.$trigger['code'].'</td>';
 				print '<td>'.$trigger['label'].'</td>';

+ 0 - 2
htdocs/admin/const.php

@@ -37,8 +37,6 @@ if (!$user->admin) {
 $rowid = GETPOST('rowid', 'int');
 $entity = GETPOST('entity', 'int');
 $action = GETPOST('action', 'aZ09');
-$update = GETPOST('update', 'alpha');
-$delete = GETPOST('delete', 'none'); // Do not use alpha here
 $debug = GETPOST('debug', 'int');
 $consts = GETPOST('const', 'array');
 $constname = GETPOST('constname', 'alphanohtml');

+ 1 - 1
htdocs/admin/defaultvalues.php

@@ -64,7 +64,7 @@ if (!$sortorder) {
 
 $defaulturl = GETPOST('defaulturl', 'alphanohtml');
 $defaultkey = GETPOST('defaultkey', 'alphanohtml');
-$defaultvalue = GETPOST('defaultvalue', 'none');
+$defaultvalue = GETPOST('defaultvalue', 'restricthtml');
 
 $defaulturl = preg_replace('/^\//', '', $defaulturl);
 

+ 14 - 8
htdocs/admin/oauth.php

@@ -27,6 +27,7 @@ require '../main.inc.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/oauth.lib.php';
 
+// $supportedoauth2array is defined into oauth.lib.php
 
 // Define $urlwithroot
 $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
@@ -96,10 +97,12 @@ print '<table class="noborder centpercent">';
 
 $i = 0;
 
-// $list is defined into oauth.lib.php
+// $list is defined into oauth.lib.php to the list of supporter OAuth providers.
 foreach ($list as $key) {
 	$supported = 0;
-	if (in_array($key[0], array_keys($supportedoauth2array))) {
+	$keyforsupportedoauth2array = $key[0];
+
+	if (in_array($keyforsupportedoauth2array, array_keys($supportedoauth2array))) {
 		$supported = 1;
 	}
 	if (!$supported) {
@@ -110,20 +113,23 @@ foreach ($list as $key) {
 
 	print '<tr class="liste_titre'.($i > 1 ? ' liste_titre_add' : '').'">';
 	// Api Name
-	$label = $langs->trans($key[0]);
-	print '<td>'.$label.'</td>';
+	$label = $langs->trans($keyforsupportedoauth2array);
+	print '<td>';
+	print img_picto('', $supportedoauth2array[$keyforsupportedoauth2array]['picto'], 'class="pictofixedwidth"');
+	print $label;
+	print '</td>';
 	print '<td>';
-	if (!empty($key[3])) {
-		print $langs->trans($key[3]);
+	if (!empty($supportedoauth2array[$keyforsupportedoauth2array]['urlforapp'])) {
+		print $langs->trans($supportedoauth2array[$keyforsupportedoauth2array]['urlforapp']);
 	}
 	print '</td>';
 	print '</tr>';
 
 	if ($supported) {
-		$redirect_uri = $urlwithroot.'/core/modules/oauth/'.$supportedoauth2array[$key[0]].'_oauthcallback.php';
+		$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'.$key[0].'" value="'.$redirect_uri.'">';
+		print '<td><input style="width: 80%" type"text" name="uri'.$keyforsupportedoauth2array.'" value="'.$redirect_uri.'">';
 		print '</td></tr>';
 	} else {
 		print '<tr class="oddeven value">';

+ 41 - 26
htdocs/admin/oauthlogintokens.php

@@ -25,17 +25,13 @@
 
 require '../main.inc.php';
 require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
-require_once DOL_DOCUMENT_ROOT.'/core/lib/oauth.lib.php'; // This define $list
+require_once DOL_DOCUMENT_ROOT.'/core/lib/oauth.lib.php'; // This define $list and $supportedoauth2array
 require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
 use OAuth\Common\Storage\DoliStorage;
 
 // Load translation files required by the page
 $langs->loadLangs(array('admin', 'printing', 'oauth'));
 
-if (!$user->admin) {
-	accessforbidden();
-}
-
 $action = GETPOST('action', 'aZ09');
 $mode = GETPOST('mode', 'alpha');
 $value = GETPOST('value', 'alpha');
@@ -50,6 +46,10 @@ if (!$mode) {
 	$mode = 'setup';
 }
 
+if (!$user->admin) {
+	accessforbidden();
+}
+
 
 /*
  * Action
@@ -122,7 +122,7 @@ $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domai
 
 $form = new Form($db);
 
-llxHeader('', $langs->trans("PrintingSetup"));
+llxHeader('', $langs->trans("TokenManager"));
 
 $linkback = '<a href="'.DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1">'.$langs->trans("BackToModuleList").'</a>';
 print load_fiche_titre($langs->trans('ConfigOAuth'), $linkback, 'title_setup');
@@ -140,7 +140,9 @@ if ($mode == 'setup' && $user->admin) {
 
 	foreach ($list as $key) {
 		$supported = 0;
-		if (in_array($key[0], array_keys($supportedoauth2array))) {
+		$keyforsupportedoauth2array = $key[0];
+
+		if (in_array($keyforsupportedoauth2array, array_keys($supportedoauth2array))) {
 			$supported = 1;
 		}
 		if (!$supported) {
@@ -148,34 +150,44 @@ if ($mode == 'setup' && $user->admin) {
 		}
 
 
-		$OAUTH_SERVICENAME = 'Unknown';
-		if ($key[0] == 'OAUTH_GITHUB_NAME') {
-			$OAUTH_SERVICENAME = 'GitHub';
+		$OAUTH_SERVICENAME = empty($supportedoauth2array[$keyforsupportedoauth2array]['name']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['name'];
+
+		// Define $shortscope, $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';
 			$urltorenew = $urlwithroot.'/core/modules/oauth/github_oauthcallback.php?shortscope='.$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 ($key[0] == 'OAUTH_GOOGLE_NAME') {
-			$OAUTH_SERVICENAME = 'Google';
+		} elseif ($keyforsupportedoauth2array == 'OAUTH_GOOGLE_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 = 'userinfo_email,userinfo_profile,cloud_print';
-			if (!empty($conf->global->OAUTH_GSUITE)) {
+			// 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';
 			}
-			//$scope.=',gmail_full';
-			$urltorenew = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?shortscope='.$shortscope.'&state='.$shortscope.'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php');
+			if (!empty($conf->global->OAUTH_GOOGLE_GMAIL)) {
+				$shortscope.=',gmail_full';
+			}
+
+			$oauthstateanticsrf = bin2hex(random_bytes(128/8));
+			$_SESSION['oauthstateanticsrf'] = $shortscope.'-'.$oauthstateanticsrf;
+
+			$urltorenew = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?shortscope='.$shortscope.'&state='.$shortscope.'-'.$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 ($key[0] == 'OAUTH_STRIPE_TEST_NAME') {
-			$OAUTH_SERVICENAME = 'StripeTest';
+		} elseif ($keyforsupportedoauth2array == 'OAUTH_STRIPE_TEST_NAME') {
 			$urltorenew = $urlwithroot.'/core/modules/oauth/stripetest_oauthcallback.php?backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php');
 			$urltodelete = '';
 			$urltocheckperms = '';
-		} elseif ($key[0] == 'OAUTH_STRIPE_LIVE_NAME') {
-			$OAUTH_SERVICENAME = 'StripeLive';
+		} elseif ($keyforsupportedoauth2array == 'OAUTH_STRIPE_LIVE_NAME') {
 			$urltorenew = $urlwithroot.'/core/modules/oauth/stripelive_oauthcallback.php?backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php');
 			$urltodelete = '';
 			$urltocheckperms = '';
@@ -230,11 +242,14 @@ if ($mode == 'setup' && $user->admin) {
 		print '<input type="hidden" name="token" value="'.newToken().'">';
 		print '<input type="hidden" name="action" value="setconst">';
 
-		print '<div class="div-table-responsive">';
+		print '<div class="div-table-responsive-no-min">';
 		print '<table class="noborder centpercent">'."\n";
 
 		print '<tr class="liste_titre">';
-		print '<th class="titlefieldcreate">'.$langs->trans($key[0]).'</th>';
+		print '<th class="titlefieldcreate">';
+		print img_picto('', $supportedoauth2array[$keyforsupportedoauth2array]['picto'], 'class="pictofixedwidth"');
+		print $langs->trans($keyforsupportedoauth2array);
+		print '</th>';
 		print '<th></th>';
 		print '<th></th>';
 		print "</tr>\n";
@@ -244,7 +259,7 @@ if ($mode == 'setup' && $user->admin) {
 		//var_dump($key);
 		print $langs->trans("OAuthIDSecret").'</td>';
 		print '<td>';
-		print $langs->trans("SeePreviousTab");
+		print '<span class="opacitymedium">'.$langs->trans("SeePreviousTab").'</span>';
 		print '</td>';
 		print '<td>';
 		print '</td>';
@@ -259,7 +274,7 @@ if ($mode == 'setup' && $user->admin) {
 		if (is_object($tokenobj)) {
 			print $langs->trans("HasAccessToken");
 		} else {
-			print $langs->trans("NoAccessToken");
+			print '<span class="opacitymedium">'.$langs->trans("NoAccessToken").'</span>';
 		}
 		print '</td>';
 		print '<td width="50%">';
@@ -346,7 +361,7 @@ if ($mode == 'setup' && $user->admin) {
 if ($mode == 'test' && $user->admin) {
 	print $langs->trans('PrintTestDesc'.$driver)."<br><br>\n";
 
-	print '<div class="div-table-responsive">';
+	print '<div class="div-table-responsive-no-min">';
 	print '<table class="noborder centpercent">';
 	if (!empty($driver)) {
 		require_once DOL_DOCUMENT_ROOT.'/core/modules/printing/'.$driver.'.modules.php';

+ 6 - 6
htdocs/comm/mailing/card.php

@@ -493,9 +493,9 @@ if (empty($reshook)) {
 	if ($action == 'add') {
 		$mesgs = array();
 
-		$object->email_from     = (string) GETPOST("from", "none"); // Must allow 'name <email>'
-		$object->email_replyto  = (string) GETPOST("replyto", "none"); // Must allow 'name <email>'
-		$object->email_errorsto = (string) GETPOST("errorsto", "none"); // Must allow 'name <email>'
+		$object->email_from     = (string) GETPOST("from", 'alphawithlgt'); // Must allow 'name <email>'
+		$object->email_replyto  = (string) GETPOST("replyto", 'alphawithlgt'); // Must allow 'name <email>'
+		$object->email_errorsto = (string) GETPOST("errorsto", 'alphawithlgt'); // Must allow 'name <email>'
 		$object->title          = (string) GETPOST("title");
 		$object->sujet          = (string) GETPOST("sujet");
 		$object->body           = (string) GETPOST("bodyemail", 'restricthtml');
@@ -531,11 +531,11 @@ if (empty($reshook)) {
 		if ($action == 'settitle') {
 			$object->title = trim(GETPOST('title', 'alpha'));
 		} elseif ($action == 'setemail_from') {
-			$object->email_from = trim(GETPOST('email_from', 'none')); // Must allow 'name <email>'
+			$object->email_from = trim(GETPOST('email_from', 'alphawithlgt')); // Must allow 'name <email>'
 		} elseif ($action == 'setemail_replyto') {
-			$object->email_replyto = trim(GETPOST('email_replyto', 'none')); // Must allow 'name <email>'
+			$object->email_replyto = trim(GETPOST('email_replyto', 'alphawithlgt')); // Must allow 'name <email>'
 		} elseif ($action == 'setemail_errorsto') {
-			$object->email_errorsto = trim(GETPOST('email_errorsto', 'none')); // Must allow 'name <email>'
+			$object->email_errorsto = trim(GETPOST('email_errorsto', 'alphawithlgt')); // Must allow 'name <email>'
 		} elseif ($action == 'settitle' && empty($object->title)) {
 			$mesg = $langs->trans("ErrorFieldRequired", $langs->transnoentities("MailTitle"));
 		} elseif ($action == 'setfrom' && empty($object->email_from)) {

+ 3 - 3
htdocs/compta/facture/class/facture.class.php

@@ -527,9 +527,9 @@ class Facture extends CommonInvoice
 
 			// Fields coming from GUI (priority on template). TODO Value of template should be used as default value on GUI so we can use here always value from GUI
 			$this->fk_project        = GETPOST('projectid', 'int') > 0 ? ((int) GETPOST('projectid', 'int')) : $_facrec->fk_project;
-			$this->note_public       = GETPOST('note_public', 'none') ? GETPOST('note_public', 'restricthtml') : $_facrec->note_public;
-			$this->note_private      = GETPOST('note_private', 'none') ? GETPOST('note_private', 'restricthtml') : $_facrec->note_private;
-			$this->model_pdf = GETPOST('model', 'alpha') ? GETPOST('model', 'alpha') : $_facrec->model_pdf;
+			$this->note_public       = GETPOSTISSET('note_public') ? GETPOST('note_public', 'restricthtml') : $_facrec->note_public;
+			$this->note_private      = GETPOSTISSET('note_private') ? GETPOST('note_private', 'restricthtml') : $_facrec->note_private;
+			$this->model_pdf = GETPOSTISSET('model') ? GETPOST('model', 'alpha') : $_facrec->model_pdf;
 			$this->cond_reglement_id = GETPOST('cond_reglement_id', 'int') > 0 ? ((int) GETPOST('cond_reglement_id', 'int')) : $_facrec->cond_reglement_id;
 			$this->mode_reglement_id = GETPOST('mode_reglement_id', 'int') > 0 ? ((int) GETPOST('mode_reglement_id', 'int')) : $_facrec->mode_reglement_id;
 			$this->fk_account        = GETPOST('fk_account') > 0 ? ((int) GETPOST('fk_account')) : $_facrec->fk_account;

+ 3 - 2
htdocs/compta/tva/card.php

@@ -196,7 +196,8 @@ if ($action == 'add' && !$cancel) {
 	}
 	$object->amount = $amount;
 	$object->label = GETPOST("label", 'alpha');
-	$object->note = GETPOST("note", 'none');
+	$object->note = GETPOST("note", 'restricthtml');
+	$object->note_private = GETPOST("note", 'restricthtml');
 
 	if (empty($object->datep)) {
 		setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("DatePayment")), null, 'errors');
@@ -236,7 +237,7 @@ if ($action == 'add' && !$cancel) {
 			$paiement->amounts      = array($object->id=>$amount); // Tableau de montant
 			$paiement->paiementtype = GETPOST("type_payment", 'alphanohtml');
 			$paiement->num_payment  = GETPOST("num_payment", 'alphanohtml');
-			$paiement->note = GETPOST("note", 'none');
+			$paiement->note = GETPOST("note", 'restricthtml');
 
 			if (!$error) {
 				$paymentid = $paiement->create($user, (int) GETPOST('closepaidtva'));

+ 7 - 3
htdocs/core/class/CSMSFile.class.php

@@ -29,8 +29,9 @@
 
 /**
  *		Class to send SMS
- *      Usage: $smsfile = new CSMSFile($subject,$sendto,$replyto,$message,$filepath,$mimetype,$filename,$cc,$ccc,$deliveryreceipt,$msgishtml,$errors_to);
- *             $smsfile->sendfile();
+ *      Usage:	$smsfile = new CSMSFile($subject,$sendto,$replyto,$message,$filepath,$mimetype,$filename,$cc,$ccc,$deliveryreceipt,$msgishtml,$errors_to);
+ *      		$smsfile->socid=...; $smsfile->contact_id=...; $smsfile->member_id=...; $smsfile->fk_project=...;
+ *             	$smsfile->sendfile();
  */
 class CSMSFile
 {
@@ -48,7 +49,8 @@ class CSMSFile
 	public $nostop;
 
 	public $socid;
-	public $contactid;
+	public $contact_id;
+	public $member_id;
 
 	public $fk_project;
 
@@ -135,6 +137,7 @@ class CSMSFile
 
 				$sms->socid = $this->socid;
 				$sms->contact_id = $this->contact_id;
+				$sms->member_id = $this->member_id;
 				$sms->project = $this->fk_project;
 
 				$res = $sms->SmsSend();
@@ -167,6 +170,7 @@ class CSMSFile
 
 					$sms->socid = $this->socid;
 					$sms->contact_id = $this->contact_id;
+					$sms->member_id = $this->member_id;
 					$sms->fk_project = $this->fk_project;
 
 					$res = $sms->SmsSend();

+ 13 - 9
htdocs/core/class/html.formadmin.class.php

@@ -73,15 +73,19 @@ class FormAdmin
 
 		$langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 12, 0, $mainlangonly);
 
-		// If the language to select is not inside the list of available language and empty value is not available, we must find
-		// an alternative as the language code to pre-select (to avoid to have first element in list pre-selected).
-		if ($selected && !array_key_exists($selected, $langs_available) && empty($showempty)) {
-			$tmparray = explode('_', $selected);
-			if (!empty($tmparray[1])) {
-				$selected = getLanguageCodeFromCountryCode($tmparray[1]);
-			}
-			if (empty($selected)) {
-				$selected = $langs->defaultlang;
+		// If empty value is not allowed and the language to select is not inside the list of available language and we must find
+		// an alternative of the language code to pre-select (to avoid to have first element in list pre-selected).
+		if ($selected && empty($showempty)) {
+			if (!is_array($selected) && !array_key_exists($selected, $langs_available)) {
+				$tmparray = explode('_', $selected);
+				if (!empty($tmparray[1])) {
+					$selected = getLanguageCodeFromCountryCode($tmparray[1]);
+				}
+				if (empty($selected)) {
+					$selected = $langs->defaultlang;
+				}
+			} else {
+				// If the preselected value is an array, we do not try to find alternative to preselect
 			}
 		}
 

+ 1 - 1
htdocs/core/customreports.php

@@ -56,7 +56,7 @@ if (!defined('USE_CUSTOM_REPORT_AS_INCLUDE')) {
 	}
 
 	$search_yaxis = GETPOST('search_yaxis', 'array');
-	$search_graph = GETPOST('search_graph', 'none');
+	$search_graph = GETPOST('search_graph', 'restricthtml');
 
 	// Load variable for pagination
 	$limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit;

+ 12 - 0
htdocs/core/db/DoliDB.class.php

@@ -70,6 +70,18 @@ abstract class DoliDB implements Database
 	/** @var string */
 	public $error;
 
+
+
+	/**
+	 *	Return the DB prefix
+	 *
+	 *	@return string		The DB prefix
+	 */
+	public function prefix()
+	{
+		return (empty($this->prefix_db) ? MAIN_DB_PREFIX : $this->prefix_db);
+	}
+
 	/**
 	 *	Format a SQL IF
 	 *

+ 4 - 4
htdocs/core/lib/functions.lib.php

@@ -641,7 +641,7 @@ function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null
 	}
 
 	// Check rule
-	if (preg_match('/^array/', $check)) {	// If 'array' or 'array:restricthtml' or 'array:aZ09'
+	if (preg_match('/^array/', $check)) {	// If 'array' or 'array:restricthtml' or 'array:aZ09' or 'array:intcomma'
 		if (!is_array($out) || empty($out)) {
 			$out = array();
 		} else {
@@ -1897,7 +1897,7 @@ function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldi
 
 	if ($object->element == 'product') {
 		$width = 80;
-		$cssclass = 'photoref';
+		$cssclass = 'photowithmargin photoref';
 		$showimage = $object->is_photo_available($conf->product->multidir_output[$entity]);
 		$maxvisiblephotos = (isset($conf->global->PRODUCT_MAX_VISIBLE_PHOTO) ? $conf->global->PRODUCT_MAX_VISIBLE_PHOTO : 5);
 		if ($conf->browser->layout == 'phone') {
@@ -3658,7 +3658,7 @@ function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $
 				'paiment', 'paragraph', 'play', 'pdf', 'phone', 'phoning', 'phoning_mobile', 'phoning_fax', 'playdisabled', 'previous', 'poll', 'pos', 'printer', 'product', 'propal', 'puce',
 				'stock', 'resize', 'service', 'stats', 'trip',
 				'security', 'setup', 'share-alt', 'sign-out', 'split', 'stripe', 'stripe-s', 'switch_off', 'switch_on', 'switch_on_red', 'tools', 'unlink', 'uparrow', 'user', 'vcard', 'wrench',
-				'github', 'jabber', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'youtube', 'google-plus-g', 'whatsapp',
+				'github', 'google', 'jabber', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'youtube', 'google-plus-g', 'whatsapp',
 				'chevron-left', 'chevron-right', 'chevron-down', 'chevron-top', 'commercial', 'companies',
 				'generic', 'home', 'hrm', 'members', 'products', 'invoicing',
 				'partnership', 'payment', 'payment_vat', 'pencil-ruler', 'preview', 'project', 'projectpub', 'projecttask', 'question', 'refresh', 'region',
@@ -3678,7 +3678,7 @@ function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $
 			if (in_array($pictowithouttext, array('card', 'bell', 'clock', 'establishment', 'generic', 'minus-square', 'object_generic', 'pdf', 'plus-square', 'timespent', 'note', 'off', 'on', 'object_bookmark', 'bookmark', 'vcard'))) {
 				$fa = 'far';
 			}
-			if (in_array($pictowithouttext, array('black-tie', 'github', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'stripe', 'stripe-s', 'youtube', 'google-plus-g', 'whatsapp'))) {
+			if (in_array($pictowithouttext, array('black-tie', 'github', 'google', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'stripe', 'stripe-s', 'youtube', 'google-plus-g', 'whatsapp'))) {
 				$fa = 'fab';
 			}
 

+ 8 - 5
htdocs/core/lib/geturl.lib.php

@@ -214,11 +214,14 @@ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation =
 				}
 			}
 
-			// Common check (local and external)
-			if (in_array($iptocheck, array('100.100.100.200'))) {
-				$info['http_code'] = 400;
-				$info['content'] = 'Error bad hostname IP (Used by Alibaba metadata). Must be an external URL.';
-				break;
+			// Common check on ip (local and external)
+			$arrayofmetadataserver = array('100.100.100.200' => 'Alibaba', '192.0.0.192'=> 'Oracle', '192.80.8.124'=>'Packet');
+			foreach ($arrayofmetadataserver as $ipofmetadataserver => $nameofmetadataserver) {
+				if ($iptocheck == $ipofmetadataserver) {
+					$info['http_code'] = 400;
+					$info['content'] = 'Error bad hostname IP (Used by '.$nameofmetadataserver.' metadata server). This IP is forbidden.';
+					break 2;	// exit the foreach and the do...
+				}
 			}
 
 			// Set CURLOPT_CONNECT_TO so curl will not try another resolution that may give a different result. Possible only on PHP v7+

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

@@ -25,13 +25,13 @@
 
 // Supported OAUTH (a provider is supported when a file xxx_oauthcallback.php is available into htdocs/core/modules/oauth)
 $supportedoauth2array = array(
-	'OAUTH_GOOGLE_NAME'=>'google',
+	'OAUTH_GOOGLE_NAME'=>array('callbackfile' => 'google', 'picto' => 'google', 'urlforapp' => 'OAUTH_GOOGLE_DESC', 'name'=>'Google'),
 );
-if ($conf->global->MAIN_FEATURES_LEVEL >= 2) {
-	$supportedoauth2array['OAUTH_STRIPE_TEST_NAME'] = 'stripetest';
-	$supportedoauth2array['OAUTH_STRIPE_LIVE_NAME'] = 'stripelive';
+if (!empty($conf->stripe->enabled)) {
+	$supportedoauth2array['OAUTH_STRIPE_TEST_NAME'] = array('callbackfile' => 'stripetest', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeTest');
+	$supportedoauth2array['OAUTH_STRIPE_LIVE_NAME'] = array('callbackfile' => 'stripelive', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeLive');
 }
-$supportedoauth2array['OAUTH_GITHUB_NAME'] = 'github';
+$supportedoauth2array['OAUTH_GITHUB_NAME'] = array('callbackfile' => 'github', 'picto' => 'github', 'urlforapp' => 'OAUTH_GITHUB_DESC', 'name'=>'GitHub');
 
 
 

+ 0 - 1
htdocs/core/modules/modPrinting.class.php

@@ -34,7 +34,6 @@ include_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php';
  */
 class modPrinting extends DolibarrModules
 {
-
 	/**
 	 *  Constructor
 	 *

+ 81 - 35
htdocs/core/modules/oauth/google_oauthcallback.php

@@ -16,6 +16,9 @@
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
 
+// This page should make the process to login and get token as described here:
+// https://developers.google.com/identity/protocols/oauth2/openid-connect#server-flow
+
 /**
  *      \file       htdocs/core/modules/oauth/google_oauthcallback.php
  *      \ingroup    oauth
@@ -70,9 +73,13 @@ $credentials = new Credentials(
 	$currentUri->getAbsoluteUri()
 );
 
+$state = GETPOST('state');
+
 $requestedpermissionsarray = array();
-if (GETPOST('state')) {
-	$requestedpermissionsarray = explode(',', GETPOST('state')); // Example: 'userinfo_email,userinfo_profile,cloud_print'. 'state' parameter is standard to store a hash value and can be used to retrieve some parameters back
+if ($state) {
+	// 'state' parameter is standard to store a hash value and can be used to retrieve some parameters back
+	$statewithscopeonly = preg_replace('/\-.*$/', '', $state);
+	$requestedpermissionsarray = explode(',', $statewithscopeonly); // Example: 'userinfo_email,userinfo_profile,openid,email,profile,cloud_print'.
 }
 if ($action != 'delete' && empty($requestedpermissionsarray)) {
 	print 'Error, parameter state is not defined';
@@ -80,6 +87,8 @@ if ($action != 'delete' && empty($requestedpermissionsarray)) {
 }
 //var_dump($requestedpermissionsarray);exit;
 
+
+
 // Instantiate the Api service using the credentials, http client and storage mechanism for the token
 // $requestedpermissionsarray contains list of scopes.
 // Conversion into URL is done by Reflection on constant with name SCOPE_scope_in_uppercase
@@ -89,7 +98,6 @@ $apiService = $serviceFactory->createService('Google', $credentials, $storage, $
 // also note that a refresh token is sent only after a prompt
 $apiService->setAccessType('offline');
 
-$apiService->setApprouvalPrompt('force');
 
 $langs->load("oauth");
 
@@ -108,48 +116,86 @@ if ($action == 'delete') {
 	exit();
 }
 
-if (!empty($_GET['code'])) {     // We are coming from oauth provider page
+if (GETPOST('code')) {     // We are coming from oauth provider page.
 	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 = isset($_GET['state']) ? $_GET['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\Google
-
-		$token = $apiService->requestAccessToken($_GET['code'], $state);
 
-		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();
+	// We must validate that the $state is the same than the one into $_SESSION['oauthstateanticsrf'], return error if not.
+	if (isset($_SESSION['oauthstateanticsrf']) && $state != $_SESSION['oauthstateanticsrf']) {
+		print 'Value for state = '.dol_escape_htmltag($state).' differs from value in $_SESSION["oauthstateanticsrf"]. Code is refused.';
+		unset($_SESSION['oauthstateanticsrf']);
+	} else {
+		// This was a callback request from service, get the token
+		try {
+			//var_dump($_GET['code']);
+			//var_dump($state);
+			//var_dump($apiService);      // OAuth\OAuth2\Service\Google
+
+			// This request the token
+			// Result is stored into object managed by class DoliStorage into includes/OAuth/Common/Storage/DoliStorage.php, so into table llx_oauth_token
+			$token = $apiService->requestAccessToken(GETPOST('code'), $state);
+
+			// Note: The extraparams has the 'id_token' than contains a lot of information about the user.
+			$extraparams = $token->getExtraParams();
+			$jwt = explode('.', $extraparams['id_token']);
+
+			// Extract the middle part, base64 decode, then json_decode it
+			if (!empty($jwt[1])) {
+				$userinfo = json_decode(base64_decode($jwt[1]), true);
+
+				// TODO
+				// We should make the 5 steps of validation of id_token
+				// Verify that the ID token is properly signed by the issuer. Google-issued tokens are signed using one of the certificates found at the URI specified in the jwks_uri metadata value of the Discovery document.
+				// Verify that the value of the iss claim in the ID token is equal to https://accounts.google.com or accounts.google.com.
+				// Verify that the value of the aud claim in the ID token is equal to your app's client ID.
+				// Verify that the expiry time (exp claim) of the ID token has not passed.
+				// If you specified a hd parameter value in the request, verify that the ID token has a hd claim that matches an accepted G Suite hosted domain.
+
+				/*
+				$useremailuniq = $userinfo['sub'];
+				$useremail = $userinfo['email'];
+				$useremailverified = $userinfo['email_verified'];
+				$username = $userinfo['name'];
+				$userfamilyname = $userinfo['family_name'];
+				$usergivenname = $userinfo['given_name'];
+				$hd = $userinfo['hd'];
+				*/
+			}
+
+			setEventMessages($langs->trans('NewTokenStored'), null, 'mesgs');
+
+			$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
-{
+} else {
+	// If we enter this page without 'code' parameter, we arrive here. this is the case when we want to get the redirect
+	// to the OAuth provider login page
 	$_SESSION["backtourlsavedbeforeoauthjump"] = $backtourl;
 
+	if (!preg_match('/^forlogin/', $state)) {
+		$apiService->setApprouvalPrompt('force');
+	}
+
 	// 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')));
+	if ($state) {
+		$url = $apiService->getAuthorizationUri(array('state' => $state));
 	} else {
 		$url = $apiService->getAuthorizationUri(); // Parameter state will be randomly generated
 	}
 
+	// Add more param
+	$url .= '&nonce='.bin2hex(random_bytes(64/8));
+	// TODO Add param hd and/or login_hint
+	if (!preg_match('/^forlogin/', $state)) {
+		//$url .= 'hd=xxx';
+	}
+
 	// we go on oauth provider authorization page
 	header('Location: '.$url);
 	exit();
@@ -160,6 +206,6 @@ if (!empty($_GET['code'])) {     // We are coming from oauth provider page
  * View
  */
 
-// No view at all, just actions
+// No view at all, just actions, so we never reach this line.
 
 $db->close();

+ 1 - 1
htdocs/core/tpl/admin_extrafields_add.tpl.php

@@ -185,7 +185,7 @@ $listofexamplesforlink = 'Societe:societe/class/societe.class.php<br>Contact:con
 <?php } else { ?>
 	<td><?php echo $form->textwithpicto($langs->trans("ComputedFormula"), $langs->trans("ComputedFormulaDesc")).$form->textwithpicto($langs->trans("Computedpersistent"), $langs->trans("ComputedpersistentDesc"), 1, 'warning'); ?></td>
 <?php } ?>
-<td class="valeur"><textarea name="computed_value" id="computed_value" class="quatrevingtpercent" rows="<?php echo ROWS_4 ?>"><?php echo (GETPOST('computed_value', 'none') ?GETPOST('computed_value', 'none') : ''); ?></textarea></td>
+<td class="valeur"><textarea name="computed_value" id="computed_value" class="quatrevingtpercent" rows="<?php echo ROWS_4 ?>"><?php echo (GETPOSTISSET('computed_value') ? GETPOST('computed_value', 'restricthtml') : ''); ?></textarea></td>
 </tr>
 <!-- Default Value (at sql setup level) -->
 <tr class="extra_default_value"><td><?php echo $langs->trans("DefaultValue").' ('.$langs->trans("Database").')'; ?></td><td class="valeur"><input id="default_value" type="text" name="default_value" size="5" value="<?php echo (GETPOST('default_value', 'alpha') ?GETPOST('default_value', 'alpha') : ''); ?>"></td></tr>

+ 28 - 0
htdocs/core/tpl/login.tpl.php

@@ -33,6 +33,8 @@ if (empty($conf) || !is_object($conf)) {
 
 require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
 
+
+
 header('Cache-Control: Public, must-revalidate');
 header("Content-type: text/html; charset=".$conf->file->character_set_client);
 
@@ -316,6 +318,32 @@ if (isset($conf->file->main_authentication) && preg_match('/openid/', $conf->fil
 	echo '</div>';
 }
 
+if (isset($conf->file->main_authentication) && preg_match('/google/', $conf->file->main_authentication)) {
+	$langs->load("users");
+
+	global $dolibarr_main_url_root;
+
+	// 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
+
+	echo '<br>';
+	echo '<div class="center" style="margin-top: 4px;">';
+
+	//$shortscope = 'userinfo_email,userinfo_profile';
+	$shortscope = 'openid,email,profile';	// For openid connect
+
+	$oauthstateanticsrf = bin2hex(random_bytes(128/8));
+	$_SESSION['oauthstateanticsrf'] = $shortscope.'-'.$oauthstateanticsrf;
+	$urltorenew = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?shortscope='.$shortscope.'&state=forlogin-'.$shortscope.'-'.$oauthstateanticsrf;
+
+	$url = $urltorenew;
+
+	print img_picto('', 'google', 'class="pictofixedwidth"').'<a class="alogin" href="'.$url.'">'.$langs->trans("LoginWith", "Google").'</a>';
+
+	echo '</div>';
+}
 
 ?>
 

+ 4 - 2
htdocs/core/triggers/interface_50_modAgenda_ActionsAuto.class.php

@@ -66,7 +66,7 @@ class InterfaceActionsAuto extends DolibarrTriggers
 	 *      $object->elementtype (->element of object to link action to)
 	 *      $object->module (if defined, elementtype in llx_actioncomm will be elementtype@module)
 	 *
-	 * @param string		$action		Event action code ('CONTRACT_MODIFY', 'RECRUITMENTCANDIDATURE_MODIFIY', ...)
+	 * @param string		$action		Event action code ('CONTRACT_MODIFY', 'RECRUITMENTCANDIDATURE_MODIFIY', or example by external module: 'SENTBYSMS'...)
 	 * @param Object		$object     Object
 	 * @param User		    $user       Object user
 	 * @param Translate 	$langs      Object langs
@@ -88,6 +88,7 @@ class InterfaceActionsAuto extends DolibarrTriggers
 		//var_dump($action.' - '.$conf->global->$key);exit;
 
 		// Do not log events not enabled for this action
+		// GUI allow to set this option only if entry exists into table llx_c_action_trigger
 		if (empty($conf->global->$key)) {
 			return 0;
 		}
@@ -887,8 +888,9 @@ class InterfaceActionsAuto extends DolibarrTriggers
 		} else {
 			// TODO Merge all previous cases into this generic one
 			// $action = BILL_DELETE, TICKET_CREATE, TICKET_MODIFY, TICKET_DELETE, CONTACT_SENTBYMAIL, RECRUITMENTCANDIDATURE_MODIFY, ...
+			// Can also be a value defined by an external module like SENTBYSMS, COMPANY_SENTBYSMS, MEMBER_SENTBYSMS, ...
 			// Note: We are here only if $conf->global->MAIN_AGENDA_ACTIONAUTO_action is on (tested at begining of this function).
-			// Note that these key can be set in agenda setup, only if defined into c_action_trigger
+			// Note that these key can be set in agenda setup, only if defined into llx_c_action_trigger
 			// Load translation files required by the page
 			if (empty($object->actionmsg2)) {
 				$langs->loadLangs(array("agenda", "other"));

+ 0 - 1
htdocs/eventorganization/conferenceorboothattendee_note.php

@@ -39,7 +39,6 @@
 //if (! defined("MAIN_LANG_DEFAULT"))        define('MAIN_LANG_DEFAULT', 'auto');					// Force lang to a particular value
 //if (! defined("MAIN_AUTHENTICATION_MODE")) define('MAIN_AUTHENTICATION_MODE', 'aloginmodule');	// Force authentication handler
 //if (! defined("NOREDIRECTBYMAINTOLOGIN"))  define('NOREDIRECTBYMAINTOLOGIN', 1);		// The main.inc.php does not make a redirect if not logged, instead show simple error message
-//if (! defined("FORCECSP"))                 define('FORCECSP', 'none');				// Disable all Content Security Policies
 //if (! defined('CSRFCHECK_WITH_TOKEN'))     define('CSRFCHECK_WITH_TOKEN', '1');		// Force use of CSRF protection with tokens even for GET
 //if (! defined('NOBROWSERNOTIF'))     		 define('NOBROWSERNOTIF', '1');				// Disable browser notification
 

+ 2 - 2
htdocs/externalsite/admin/index.php

@@ -57,7 +57,7 @@ if ($action == 'update') {
 	$label  = GETPOST('EXTERNALSITE_LABEL', 'alphanohtml');
 
 	// exturl can be an url or a HTML string
-	$exturl = GETPOST('EXTERNALSITE_URL', 'none');
+	$exturl = GETPOST('EXTERNALSITE_URL', 'restricthtml');
 	$exturl = dol_string_onlythesehtmltags($exturl, 1, 1, 0, 1);
 	$exturl = dol_string_onlythesehtmlattributes($exturl);
 
@@ -110,7 +110,7 @@ print '<tr class="oddeven">';
 print '<td class="fieldrequired">'.$langs->trans("ExternalSiteURL")."</td>";
 print '<td><textarea class="flat minwidth500" name="EXTERNALSITE_URL">';
 
-$exturl = GETPOST('EXTERNALSITE_URL', 'none');
+$exturl = GETPOST('EXTERNALSITE_URL', 'restricthtml');
 $exturl = dol_string_onlythesehtmltags($exturl, 1, 1, 0, 1);
 $exturl = dol_string_onlythesehtmlattributes($exturl);
 

+ 0 - 1
htdocs/hrm/position.php

@@ -42,7 +42,6 @@
 //if (! defined("MAIN_LANG_DEFAULT"))        define('MAIN_LANG_DEFAULT', 'auto');					// Force lang to a particular value
 //if (! defined("MAIN_AUTHENTICATION_MODE")) define('MAIN_AUTHENTICATION_MODE', 'aloginmodule');	// Force authentication handler
 //if (! defined("NOREDIRECTBYMAINTOLOGIN"))  define('NOREDIRECTBYMAINTOLOGIN', 1);		// The main.inc.php does not make a redirect if not logged, instead show simple error message
-//if (! defined("FORCECSP"))                 define('FORCECSP', 'none');				// Disable all Content Security Policies
 //if (! defined('CSRFCHECK_WITH_TOKEN'))     define('CSRFCHECK_WITH_TOKEN', '1');		// Force use of CSRF protection with tokens even for GET
 //if (! defined('NOBROWSERNOTIF'))     		 define('NOBROWSERNOTIF', '1');				// Disable browser notification
 

+ 189 - 187
htdocs/includes/OAuth/OAuth2/Service/Google.php

@@ -13,194 +13,196 @@ use OAuth\Common\Http\Uri\Uri;
 
 class Google extends AbstractService
 {
-    /**
-     * Defined scopes - More scopes are listed here:
-     * https://developers.google.com/oauthplayground/
-     *
-     * Make a pull request if you need more scopes.
-     */
-
-    // Basic
-    const SCOPE_EMAIL                       = 'email';
-    const SCOPE_PROFILE                     = 'profile';
-
-    const SCOPE_USERINFO_EMAIL              = 'https://www.googleapis.com/auth/userinfo.email';
-    const SCOPE_USERINFO_PROFILE            = 'https://www.googleapis.com/auth/userinfo.profile';
-
-    // Google+
-    const SCOPE_GPLUS_ME                    = 'https://www.googleapis.com/auth/plus.me';
-    const SCOPE_GPLUS_LOGIN                 = 'https://www.googleapis.com/auth/plus.login';
-    const SCOPE_GPLUS_CIRCLES_READ          = 'https://www.googleapis.com/auth/plus.circles.read';
-    const SCOPE_GPLUS_CIRCLES_WRITE         = 'https://www.googleapis.com/auth/plus.circles.write';
-    const SCOPE_GPLUS_STREAM_READ           = 'https://www.googleapis.com/auth/plus.stream.read';
-    const SCOPE_GPLUS_STREAM_WRITE          = 'https://www.googleapis.com/auth/plus.stream.write';
-    const SCOPE_GPLUS_MEDIA                 = 'https://www.googleapis.com/auth/plus.media.upload';
-
-    // Google Drive
-    const SCOPE_DOCUMENTSLIST               = 'https://docs.google.com/feeds/';
-    const SCOPE_SPREADSHEETS                = 'https://spreadsheets.google.com/feeds/';
-    const SCOPE_GOOGLEDRIVE                 = 'https://www.googleapis.com/auth/drive';
-    const SCOPE_DRIVE_APPS                  = 'https://www.googleapis.com/auth/drive.appdata';
-    const SCOPE_DRIVE_APPS_READ_ONLY        = 'https://www.googleapis.com/auth/drive.apps.readonly';
-    const SCOPE_GOOGLEDRIVE_FILES           = 'https://www.googleapis.com/auth/drive.file';
-    const SCOPE_DRIVE_METADATA_READ_ONLY    = 'https://www.googleapis.com/auth/drive.metadata.readonly';
-    const SCOPE_DRIVE_READ_ONLY             = 'https://www.googleapis.com/auth/drive.readonly';
-    const SCOPE_DRIVE_SCRIPTS               = 'https://www.googleapis.com/auth/drive.scripts';
-
-    // Cloud Print
-    const SCOPE_CLOUD_PRINT                 = 'https://www.googleapis.com/auth/cloudprint';
-
-    // Adwords
-    const SCOPE_ADSENSE                     = 'https://www.googleapis.com/auth/adsense';
-    const SCOPE_ADWORDS                     = 'https://www.googleapis.com/auth/adwords/';
-    const SCOPE_GAN                         = 'https://www.googleapis.com/auth/gan'; // google affiliate network...?
-
-    // Google Analytics
-    const SCOPE_ANALYTICS                   = 'https://www.googleapis.com/auth/analytics';
-    const SCOPE_ANALYTICS_EDIT              = 'https://www.googleapis.com/auth/analytics.edit';
-    const SCOPE_ANALYTICS_MANAGE_USERS      = 'https://www.googleapis.com/auth/analytics.manage.users';
-    const SCOPE_ANALYTICS_READ_ONLY         = 'https://www.googleapis.com/auth/analytics.readonly';
-
-    //Gmail
-    const SCOPE_GMAIL_MODIFY                = 'https://www.googleapis.com/auth/gmail.modify';
-    const SCOPE_GMAIL_READONLY              = 'https://www.googleapis.com/auth/gmail.readonly';
-    const SCOPE_GMAIL_COMPOSE               = 'https://www.googleapis.com/auth/gmail.compose';
-    const SCOPE_GMAIL_SEND                  = 'https://www.googleapis.com/auth/gmail.send';
-    const SCOPE_GMAIL_INSERT                = 'https://www.googleapis.com/auth/gmail.insert';
-    const SCOPE_GMAIL_LABELS                = 'https://www.googleapis.com/auth/gmail.labels';
-    const SCOPE_GMAIL_FULL                  = 'https://mail.google.com/';
-
-    // Other services
-    const SCOPE_BOOKS                       = 'https://www.googleapis.com/auth/books';
-    const SCOPE_BLOGGER                     = 'https://www.googleapis.com/auth/blogger';
-    const SCOPE_CALENDAR                    = 'https://www.googleapis.com/auth/calendar';
-    const SCOPE_CALENDAR_READ_ONLY          = 'https://www.googleapis.com/auth/calendar.readonly';
-    const SCOPE_CONTACT                     = 'https://www.google.com/m8/feeds/';
-    const SCOPE_CONTACTS_RO                 = 'https://www.googleapis.com/auth/contacts.readonly';
-    const SCOPE_CHROMEWEBSTORE              = 'https://www.googleapis.com/auth/chromewebstore.readonly';
-    const SCOPE_GMAIL                       = 'https://mail.google.com/mail/feed/atom';
-    const SCOPE_GMAIL_IMAP_SMTP             = 'https://mail.google.com';
-    const SCOPE_PICASAWEB                   = 'https://picasaweb.google.com/data/';
-    const SCOPE_SITES                       = 'https://sites.google.com/feeds/';
-    const SCOPE_URLSHORTENER                = 'https://www.googleapis.com/auth/urlshortener';
-    const SCOPE_WEBMASTERTOOLS              = 'https://www.google.com/webmasters/tools/feeds/';
-    const SCOPE_TASKS                       = 'https://www.googleapis.com/auth/tasks';
-
-    // Cloud services
-    const SCOPE_CLOUDSTORAGE                = 'https://www.googleapis.com/auth/devstorage.read_write';
-    const SCOPE_CONTENTFORSHOPPING          = 'https://www.googleapis.com/auth/structuredcontent'; // what even is this
-    const SCOPE_USER_PROVISIONING           = 'https://apps-apis.google.com/a/feeds/user/';
-    const SCOPE_GROUPS_PROVISIONING         = 'https://apps-apis.google.com/a/feeds/groups/';
-    const SCOPE_NICKNAME_PROVISIONING       = 'https://apps-apis.google.com/a/feeds/alias/';
-
-    // Old
-    const SCOPE_ORKUT                       = 'https://www.googleapis.com/auth/orkut';
-    const SCOPE_GOOGLELATITUDE =
-        'https://www.googleapis.com/auth/latitude.all.best https://www.googleapis.com/auth/latitude.all.city';
-    const SCOPE_OPENID                      = 'openid';
-
-    // YouTube
-    const SCOPE_YOUTUBE_GDATA               = 'https://gdata.youtube.com';
-    const SCOPE_YOUTUBE_ANALYTICS_MONETARY  = 'https://www.googleapis.com/auth/yt-analytics-monetary.readonly';
-    const SCOPE_YOUTUBE_ANALYTICS           = 'https://www.googleapis.com/auth/yt-analytics.readonly';
-    const SCOPE_YOUTUBE                     = 'https://www.googleapis.com/auth/youtube';
-    const SCOPE_YOUTUBE_READ_ONLY           = 'https://www.googleapis.com/auth/youtube.readonly';
-    const SCOPE_YOUTUBE_UPLOAD              = 'https://www.googleapis.com/auth/youtube.upload';
-    const SCOPE_YOUTUBE_PARTNER             = 'https://www.googleapis.com/auth/youtubepartner';
-    const SCOPE_YOUTUBE_PARTNER_AUDIT       = 'https://www.googleapis.com/auth/youtubepartner-channel-audit';
-
-    // Google Glass
-    const SCOPE_GLASS_TIMELINE              = 'https://www.googleapis.com/auth/glass.timeline';
-    const SCOPE_GLASS_LOCATION              = 'https://www.googleapis.com/auth/glass.location';
-
-    // Android Publisher
-    const SCOPE_ANDROID_PUBLISHER           = 'https://www.googleapis.com/auth/androidpublisher';
-
-
-    // Google Gsuite
+	/**
+	 * Defined scopes - More scopes are listed here:
+	 * https://developers.google.com/oauthplayground/
+	 *
+	 * Make a pull request if you need more scopes.
+	 */
+
+	// Basic
+	const SCOPE_EMAIL                       = 'email';
+	const SCOPE_PROFILE                     = 'profile';
+
+	const SCOPE_USERINFO_EMAIL              = 'https://www.googleapis.com/auth/userinfo.email';
+	const SCOPE_USERINFO_PROFILE            = 'https://www.googleapis.com/auth/userinfo.profile';
+
+	// Google+
+	const SCOPE_GPLUS_ME                    = 'https://www.googleapis.com/auth/plus.me';
+	const SCOPE_GPLUS_LOGIN                 = 'https://www.googleapis.com/auth/plus.login';
+	const SCOPE_GPLUS_CIRCLES_READ          = 'https://www.googleapis.com/auth/plus.circles.read';
+	const SCOPE_GPLUS_CIRCLES_WRITE         = 'https://www.googleapis.com/auth/plus.circles.write';
+	const SCOPE_GPLUS_STREAM_READ           = 'https://www.googleapis.com/auth/plus.stream.read';
+	const SCOPE_GPLUS_STREAM_WRITE          = 'https://www.googleapis.com/auth/plus.stream.write';
+	const SCOPE_GPLUS_MEDIA                 = 'https://www.googleapis.com/auth/plus.media.upload';
+
+	// Google Drive
+	const SCOPE_DOCUMENTSLIST               = 'https://docs.google.com/feeds/';
+	const SCOPE_SPREADSHEETS                = 'https://spreadsheets.google.com/feeds/';
+	const SCOPE_GOOGLEDRIVE                 = 'https://www.googleapis.com/auth/drive';
+	const SCOPE_DRIVE_APPS                  = 'https://www.googleapis.com/auth/drive.appdata';
+	const SCOPE_DRIVE_APPS_READ_ONLY        = 'https://www.googleapis.com/auth/drive.apps.readonly';
+	const SCOPE_GOOGLEDRIVE_FILES           = 'https://www.googleapis.com/auth/drive.file';
+	const SCOPE_DRIVE_METADATA_READ_ONLY    = 'https://www.googleapis.com/auth/drive.metadata.readonly';
+	const SCOPE_DRIVE_READ_ONLY             = 'https://www.googleapis.com/auth/drive.readonly';
+	const SCOPE_DRIVE_SCRIPTS               = 'https://www.googleapis.com/auth/drive.scripts';
+
+	// Cloud Print
+	const SCOPE_CLOUD_PRINT                 = 'https://www.googleapis.com/auth/cloudprint';
+
+	// Adwords
+	const SCOPE_ADSENSE                     = 'https://www.googleapis.com/auth/adsense';
+	const SCOPE_ADWORDS                     = 'https://www.googleapis.com/auth/adwords/';
+	const SCOPE_GAN                         = 'https://www.googleapis.com/auth/gan'; // google affiliate network...?
+
+	// Google Analytics
+	const SCOPE_ANALYTICS                   = 'https://www.googleapis.com/auth/analytics';
+	const SCOPE_ANALYTICS_EDIT              = 'https://www.googleapis.com/auth/analytics.edit';
+	const SCOPE_ANALYTICS_MANAGE_USERS      = 'https://www.googleapis.com/auth/analytics.manage.users';
+	const SCOPE_ANALYTICS_READ_ONLY         = 'https://www.googleapis.com/auth/analytics.readonly';
+
+	//Gmail
+	const SCOPE_GMAIL_MODIFY                = 'https://www.googleapis.com/auth/gmail.modify';
+	const SCOPE_GMAIL_READONLY              = 'https://www.googleapis.com/auth/gmail.readonly';
+	const SCOPE_GMAIL_COMPOSE               = 'https://www.googleapis.com/auth/gmail.compose';
+	const SCOPE_GMAIL_SEND                  = 'https://www.googleapis.com/auth/gmail.send';
+	const SCOPE_GMAIL_INSERT                = 'https://www.googleapis.com/auth/gmail.insert';
+	const SCOPE_GMAIL_LABELS                = 'https://www.googleapis.com/auth/gmail.labels';
+	const SCOPE_GMAIL_FULL                  = 'https://mail.google.com/';
+
+	// Other services
+	const SCOPE_BOOKS                       = 'https://www.googleapis.com/auth/books';
+	const SCOPE_BLOGGER                     = 'https://www.googleapis.com/auth/blogger';
+	const SCOPE_CALENDAR                    = 'https://www.googleapis.com/auth/calendar';
+	const SCOPE_CALENDAR_READ_ONLY          = 'https://www.googleapis.com/auth/calendar.readonly';
+	const SCOPE_CONTACT                     = 'https://www.google.com/m8/feeds/';
+	const SCOPE_CONTACTS_RO                 = 'https://www.googleapis.com/auth/contacts.readonly';
+	const SCOPE_CHROMEWEBSTORE              = 'https://www.googleapis.com/auth/chromewebstore.readonly';
+	const SCOPE_GMAIL                       = 'https://mail.google.com/mail/feed/atom';
+	const SCOPE_GMAIL_IMAP_SMTP             = 'https://mail.google.com';
+	const SCOPE_PICASAWEB                   = 'https://picasaweb.google.com/data/';
+	const SCOPE_SITES                       = 'https://sites.google.com/feeds/';
+	const SCOPE_URLSHORTENER                = 'https://www.googleapis.com/auth/urlshortener';
+	const SCOPE_WEBMASTERTOOLS              = 'https://www.google.com/webmasters/tools/feeds/';
+	const SCOPE_TASKS                       = 'https://www.googleapis.com/auth/tasks';
+
+	// Cloud services
+	const SCOPE_CLOUDSTORAGE                = 'https://www.googleapis.com/auth/devstorage.read_write';
+	const SCOPE_CONTENTFORSHOPPING          = 'https://www.googleapis.com/auth/structuredcontent'; // what even is this
+	const SCOPE_USER_PROVISIONING           = 'https://apps-apis.google.com/a/feeds/user/';
+	const SCOPE_GROUPS_PROVISIONING         = 'https://apps-apis.google.com/a/feeds/groups/';
+	const SCOPE_NICKNAME_PROVISIONING       = 'https://apps-apis.google.com/a/feeds/alias/';
+
+	// Old
+	const SCOPE_ORKUT                       = 'https://www.googleapis.com/auth/orkut';
+	const SCOPE_GOOGLELATITUDE =
+		'https://www.googleapis.com/auth/latitude.all.best https://www.googleapis.com/auth/latitude.all.city';
+	const SCOPE_OPENID                      = 'openid';
+
+	// YouTube
+	const SCOPE_YOUTUBE_GDATA               = 'https://gdata.youtube.com';
+	const SCOPE_YOUTUBE_ANALYTICS_MONETARY  = 'https://www.googleapis.com/auth/yt-analytics-monetary.readonly';
+	const SCOPE_YOUTUBE_ANALYTICS           = 'https://www.googleapis.com/auth/yt-analytics.readonly';
+	const SCOPE_YOUTUBE                     = 'https://www.googleapis.com/auth/youtube';
+	const SCOPE_YOUTUBE_READ_ONLY           = 'https://www.googleapis.com/auth/youtube.readonly';
+	const SCOPE_YOUTUBE_UPLOAD              = 'https://www.googleapis.com/auth/youtube.upload';
+	const SCOPE_YOUTUBE_PARTNER             = 'https://www.googleapis.com/auth/youtubepartner';
+	const SCOPE_YOUTUBE_PARTNER_AUDIT       = 'https://www.googleapis.com/auth/youtubepartner-channel-audit';
+
+	// Google Glass
+	const SCOPE_GLASS_TIMELINE              = 'https://www.googleapis.com/auth/glass.timeline';
+	const SCOPE_GLASS_LOCATION              = 'https://www.googleapis.com/auth/glass.location';
+
+	// Android Publisher
+	const SCOPE_ANDROID_PUBLISHER           = 'https://www.googleapis.com/auth/androidpublisher';
+
+
+	// Google Gsuite
 	const SCOPE_ADMIN_DIRECTORY_USER =        "https://www.googleapis.com/auth/admin.directory.user";
 	const SCOPE_ADMIN_DIRECTORY_CUSTOMER =        "https://www.googleapis.com/auth/admin.directory.customer";
 
-    protected $accessType = 'online';
-
-    public function __construct(
-        CredentialsInterface $credentials,
-        ClientInterface $httpClient,
-        TokenStorageInterface $storage,
-        $scopes = array(),
-        UriInterface $baseApiUri = null
-    ) {
-        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true);
-
-        if (null === $baseApiUri) {
-            $this->baseApiUri = new Uri('https://www.googleapis.com/oauth2/v1/');
-        }
-    }
-
-    public function setAccessType($accessType)
-    {
-        if (!in_array($accessType, array('online', 'offline'), true)) {
-            throw new InvalidAccessTypeException('Invalid accessType, expected either online or offline');
-        }
-        $this->accessType = $accessType;
-    }
-
-    // LDR CHANGE Add approval_prompt to force the prompt if value is set to 'force' so it force return of a "refresh token" in addition to "standard token"
-    public $approvalPrompt='auto';
-    public function setApprouvalPrompt($prompt)
-    {
-        if (!in_array($prompt, array('auto', 'force'), true)) {
-            // @todo Maybe could we rename this exception
-            throw new InvalidAccessTypeException('Invalid approuvalPrompt, expected either auto or force.');
-        }
-        $this->approvalPrompt = $prompt;
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function getAuthorizationEndpoint()
-    {
-        // LDR CHANGE Add approval_prompt to force the prompt if value is set to 'force' so it force return of a "refresh token" in addition to "standard token"
-        //return new Uri('https://accounts.google.com/o/oauth2/auth?access_type='.$this->accessType);
-        return new Uri('https://accounts.google.com/o/oauth2/auth?'.($this->approvalPrompt?'approval_prompt='.$this->approvalPrompt.'&':'').'access_type='.$this->accessType);
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function getAccessTokenEndpoint()
-    {
-        return new Uri('https://accounts.google.com/o/oauth2/token');
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    protected function parseAccessTokenResponse($responseBody)
-    {
-        $data = json_decode($responseBody, true);
-
-        if (null === $data || !is_array($data)) {
-            throw new TokenResponseException('Unable to parse response.');
-        } elseif (isset($data['error'])) {
-            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
-        }
-
-        $token = new StdOAuth2Token();
-        $token->setAccessToken($data['access_token']);
-        $token->setLifetime($data['expires_in']);
-
-        if (isset($data['refresh_token'])) {
-            $token->setRefreshToken($data['refresh_token']);
-            unset($data['refresh_token']);
-        }
-
-        unset($data['access_token']);
-        unset($data['expires_in']);
-
-        $token->setExtraParams($data);
-
-        return $token;
-    }
+	protected $accessType = 'online';
+
+	public function __construct(
+		CredentialsInterface $credentials,
+		ClientInterface $httpClient,
+		TokenStorageInterface $storage,
+		$scopes = array(),
+		UriInterface $baseApiUri = null
+	) {
+		parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true);
+
+		if (null === $baseApiUri) {
+			$this->baseApiUri = new Uri('https://www.googleapis.com/oauth2/v1/');
+		}
+	}
+
+	public function setAccessType($accessType)
+	{
+		if (!in_array($accessType, array('online', 'offline'), true)) {
+			throw new InvalidAccessTypeException('Invalid accessType, expected either online or offline');
+		}
+		$this->accessType = $accessType;
+	}
+
+	// LDR CHANGE Add approval_prompt to force the prompt if value is set to 'force' so it force return of a "refresh token" in addition to "standard token"
+	public $approvalPrompt='auto';
+	public function setApprouvalPrompt($prompt)
+	{
+		if (!in_array($prompt, array('auto', 'force'), true)) {
+			// @todo Maybe could we rename this exception
+			throw new InvalidAccessTypeException('Invalid approuvalPrompt, expected either auto or force.');
+		}
+		$this->approvalPrompt = $prompt;
+	}
+
+	/**
+	 * {@inheritdoc}
+	 */
+	public function getAuthorizationEndpoint()
+	{
+		// LDR CHANGE Add approval_prompt to force the prompt if value is set to 'force' so it force return of a "refresh token" in addition to "standard token"
+		//return new Uri('https://accounts.google.com/o/oauth2/auth?access_type='.$this->accessType);
+		$url = 'https://accounts.google.com/o/oauth2/auth?'.($this->approvalPrompt?'approval_prompt='.$this->approvalPrompt.'&':'').'access_type='.$this->accessType;
+
+		return new Uri($url);
+	}
+
+	/**
+	 * {@inheritdoc}
+	 */
+	public function getAccessTokenEndpoint()
+	{
+		return new Uri('https://accounts.google.com/o/oauth2/token');
+	}
+
+	/**
+	 * {@inheritdoc}
+	 */
+	protected function parseAccessTokenResponse($responseBody)
+	{
+		$data = json_decode($responseBody, true);
+
+		if (null === $data || !is_array($data)) {
+			throw new TokenResponseException('Unable to parse response.');
+		} elseif (isset($data['error'])) {
+			throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+		}
+
+		$token = new StdOAuth2Token();
+		$token->setAccessToken($data['access_token']);
+		$token->setLifetime($data['expires_in']);
+
+		if (isset($data['refresh_token'])) {
+			$token->setRefreshToken($data['refresh_token']);
+			unset($data['refresh_token']);
+		}
+
+		unset($data['access_token']);
+		unset($data['expires_in']);
+
+		$token->setExtraParams($data);
+
+		return $token;
+	}
 }

+ 2 - 1
htdocs/langs/en_US/other.lang

@@ -272,7 +272,8 @@ ProjectCreatedByEmailCollector=Project created by email collector from email MSG
 TicketCreatedByEmailCollector=Ticket created by email collector from email MSGID %s
 OpeningHoursFormatDesc=Use a - to separate opening and closing hours.<br>Use a space to enter different ranges.<br>Example: 8-12 14-18
 SuffixSessionName=Suffix for session name
-
+LoginWith=Login with %s
+ 
 ##### Export #####
 ExportsArea=Exports area
 AvailableFormats=Available formats

+ 1 - 1
htdocs/main.inc.php

@@ -661,7 +661,7 @@ if (!defined('NOLOGIN')) {
 		// Verification security graphic code
 		if ($test && GETPOST("username", "alpha", 2) && !empty($conf->global->MAIN_SECURITY_ENABLECAPTCHA) && !isset($_SESSION['dol_bypass_antispam'])) {
 			$sessionkey = 'dol_antispam_value';
-			$ok = (array_key_exists($sessionkey, $_SESSION) === true && (strtolower($_SESSION[$sessionkey]) === strtolower(GETPOST('code', 'none'))));
+			$ok = (array_key_exists($sessionkey, $_SESSION) === true && (strtolower($_SESSION[$sessionkey]) === strtolower(GETPOST('code', 'restricthtml'))));
 
 			// Check code
 			if (!$ok) {

+ 1 - 1
htdocs/modulebuilder/index.php

@@ -767,7 +767,7 @@ if ($dirins && $action == 'addlanguage' && !empty($module)) {
 
 // remove/delete File
 if ($dirins && $action == 'confirm_removefile' && !empty($module)) {
-	$relativefilename = dol_sanitizePathName(GETPOST('file', 'none'));
+	$relativefilename = dol_sanitizePathName(GETPOST('file', 'restricthtml'));
 	if ($relativefilename) {
 		$dirnametodelete = dirname($relativefilename);
 		$filetodelete = $dirins.'/'.$relativefilename;

+ 0 - 1
htdocs/modulebuilder/template/scripts/mymodule.php

@@ -40,7 +40,6 @@
 //if (! defined("MAIN_LANG_DEFAULT"))        define('MAIN_LANG_DEFAULT', 'auto');					// Force lang to a particular value
 //if (! defined("MAIN_AUTHENTICATION_MODE")) define('MAIN_AUTHENTICATION_MODE', 'aloginmodule');	// Force authentication handler
 //if (! defined("NOREDIRECTBYMAINTOLOGIN"))  define('NOREDIRECTBYMAINTOLOGIN', 1);		// The main.inc.php does not make a redirect if not logged, instead show simple error message
-//if (! defined("FORCECSP"))                 define('FORCECSP', 'none');				// Disable all Content Security Policies
 //if (! defined('CSRFCHECK_WITH_TOKEN'))     define('CSRFCHECK_WITH_TOKEN', '1');		// Force use of CSRF protection with tokens even for GET
 //if (! defined('NOBROWSERNOTIF'))     		 define('NOBROWSERNOTIF', '1');				// Disable browser notification
 if (!defined('NOSESSION')) define('NOSESSION', '1');	// On CLI mode, no need to use web sessions

+ 151 - 31
htdocs/product/price.php

@@ -143,15 +143,35 @@ if (empty($reshook)) {
 			$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
 			$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($mysoc->country_code)."'";
 			$sql .= " AND t.taux = ".((float) $tva_tx)." AND t.active = 1";
-			$sql .= " AND t.code ='".$db->escape($vatratecode)."'";
+			$sql .= " AND t.code = '".$db->escape($vatratecode)."'";
+			$resql = $db->query($sql);
+			if ($resql) {
+				$obj = $db->fetch_object($resql);
+				if ($obj) {
+					$npr = $obj->recuperableonly;
+					$localtax1 = $obj->localtax1;
+					$localtax2 = $obj->localtax2;
+					$localtax1_type = $obj->localtax1_type;
+					$localtax2_type = $obj->localtax2_type;
+				}
+			}
+		} else {
+			// Get record with empty code
+			$sql = "SELECT t.rowid, t.code, t.recuperableonly, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
+			$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
+			$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($mysoc->country_code)."'";
+			$sql .= " AND t.taux = ".((float) $tva_tx)." AND t.active = 1";
+			$sql .= " AND t.code = ''";
 			$resql = $db->query($sql);
 			if ($resql) {
 				$obj = $db->fetch_object($resql);
-				$npr = $obj->recuperableonly;
-				$localtax1 = $obj->localtax1;
-				$localtax2 = $obj->localtax2;
-				$localtax1_type = $obj->localtax1_type;
-				$localtax2_type = $obj->localtax2_type;
+				if ($obj) {
+					$npr = $obj->recuperableonly;
+					$localtax1 = $obj->localtax1;
+					$localtax2 = $obj->localtax2;
+					$localtax1_type = $obj->localtax1_type;
+					$localtax2_type = $obj->localtax2_type;
+				}
 			}
 		}
 
@@ -258,11 +278,37 @@ if (empty($reshook)) {
 					$resql = $db->query($sql);
 					if ($resql) {
 						$obj = $db->fetch_object($resql);
-						$npr = $obj->recuperableonly;
-						$localtax1 = $obj->localtax1;
-						$localtax2 = $obj->localtax2;
-						$localtax1_type = $obj->localtax1_type;
-						$localtax2_type = $obj->localtax2_type;
+						if ($obj) {
+							$npr = $obj->recuperableonly;
+							$localtax1 = $obj->localtax1;
+							$localtax2 = $obj->localtax2;
+							$localtax1_type = $obj->localtax1_type;
+							$localtax2_type = $obj->localtax2_type;
+						}
+
+						// If spain, we don't use the localtax found into tax record in database with same code, but using the get_localtax rule.
+						if (in_array($mysoc->country_code, array('ES'))) {
+							$localtax1 = get_localtax($tva_tx, 1);
+							$localtax2 = get_localtax($tva_tx, 2);
+						}
+					}
+				} else {
+					// Get record with empty code
+					$sql = "SELECT t.rowid, t.code, t.recuperableonly, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
+					$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
+					$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($mysoc->country_code)."'";
+					$sql .= " AND t.taux = ".((float) $tva_tx)." AND t.active = 1";
+					$sql .= " AND t.code = ''";
+					$resql = $db->query($sql);
+					if ($resql) {
+						$obj = $db->fetch_object($resql);
+						if ($obj) {
+							$npr = $obj->recuperableonly;
+							$localtax1 = $obj->localtax1;
+							$localtax2 = $obj->localtax2;
+							$localtax1_type = $obj->localtax1_type;
+							$localtax2_type = $obj->localtax2_type;
+						}
 					}
 				}
 
@@ -311,19 +357,40 @@ if (empty($reshook)) {
 				$resql = $db->query($sql);
 				if ($resql) {
 					$obj = $db->fetch_object($resql);
-					$npr = $obj->recuperableonly;
-					$localtax1 = $obj->localtax1;
-					$localtax2 = $obj->localtax2;
-					$localtax1_type = $obj->localtax1_type;
-					$localtax2_type = $obj->localtax2_type;
+					if ($obj) {
+						$npr = $obj->recuperableonly;
+						$localtax1 = $obj->localtax1;
+						$localtax2 = $obj->localtax2;
+						$localtax1_type = $obj->localtax1_type;
+						$localtax2_type = $obj->localtax2_type;
+					}
 
-					// If spain, we don't use the localtax found into tax record in database with same code, but using the get_localtax rule
+					// If spain, we don't use the localtax found into tax record in database with same code, but using the get_localtax rule.
 					if (in_array($mysoc->country_code, array('ES'))) {
 						$localtax1 = get_localtax($tva_tx, 1);
 						$localtax2 = get_localtax($tva_tx, 2);
 					}
 				}
+			} else {
+				// Get record with empty code
+				$sql = "SELECT t.rowid, t.code, t.recuperableonly, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
+				$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
+				$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($mysoc->country_code)."'";
+				$sql .= " AND t.taux = ".((float) $tva_tx)." AND t.active = 1";
+				$sql .= " AND t.code = ''";
+				$resql = $db->query($sql);
+				if ($resql) {
+					$obj = $db->fetch_object($resql);
+					if ($obj) {
+						$npr = $obj->recuperableonly;
+						$localtax1 = $obj->localtax1;
+						$localtax2 = $obj->localtax2;
+						$localtax1_type = $obj->localtax1_type;
+						$localtax2_type = $obj->localtax2_type;
+					}
+				}
 			}
+
 			$pricestoupdate[0] = array(
 				'price' => $newprice,
 				'price_min' => $newprice_min,
@@ -534,11 +601,37 @@ if (empty($reshook)) {
 			$resql = $db->query($sql);
 			if ($resql) {
 				$obj = $db->fetch_object($resql);
-				$npr = $obj->recuperableonly;
-				$localtax1 = $obj->localtax1;
-				$localtax2 = $obj->localtax2;
-				$localtax1_type = $obj->localtax1_type;
-				$localtax2_type = $obj->localtax2_type;
+				if ($obj) {
+					$npr = $obj->recuperableonly;
+					$localtax1 = $obj->localtax1;
+					$localtax2 = $obj->localtax2;
+					$localtax1_type = $obj->localtax1_type;
+					$localtax2_type = $obj->localtax2_type;
+				}
+
+				// If spain, we don't use the localtax found into tax record in database with same code, but using the get_localtax rule.
+				if (in_array($mysoc->country_code, array('ES'))) {
+					$localtax1 = get_localtax($tva_tx, 1);
+					$localtax2 = get_localtax($tva_tx, 2);
+				}
+			}
+		} else {
+			// Get record with empty code
+			$sql = "SELECT t.rowid, t.code, t.recuperableonly, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
+			$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
+			$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($mysoc->country_code)."'";
+			$sql .= " AND t.taux = ".((float) $tva_tx)." AND t.active = 1";
+			$sql .= " AND t.code = ''";
+			$resql = $db->query($sql);
+			if ($resql) {
+				$obj = $db->fetch_object($resql);
+				if ($obj) {
+					$npr = $obj->recuperableonly;
+					$localtax1 = $obj->localtax1;
+					$localtax2 = $obj->localtax2;
+					$localtax1_type = $obj->localtax1_type;
+					$localtax2_type = $obj->localtax2_type;
+				}
 			}
 		}
 
@@ -627,11 +720,37 @@ if (empty($reshook)) {
 			$resql = $db->query($sql);
 			if ($resql) {
 				$obj = $db->fetch_object($resql);
-				$npr = $obj->recuperableonly;
-				$localtax1 = $obj->localtax1;
-				$localtax2 = $obj->localtax2;
-				$localtax1_type = $obj->localtax1_type;
-				$localtax2_type = $obj->localtax2_type;
+				if ($obj) {
+					$npr = $obj->recuperableonly;
+					$localtax1 = $obj->localtax1;
+					$localtax2 = $obj->localtax2;
+					$localtax1_type = $obj->localtax1_type;
+					$localtax2_type = $obj->localtax2_type;
+				}
+
+				// If spain, we don't use the localtax found into tax record in database with same code, but using the get_localtax rule.
+				if (in_array($mysoc->country_code, array('ES'))) {
+					$localtax1 = get_localtax($tva_tx, 1);
+					$localtax2 = get_localtax($tva_tx, 2);
+				}
+			}
+		} else {
+			// Get record with empty code
+			$sql = "SELECT t.rowid, t.code, t.recuperableonly, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
+			$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
+			$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($mysoc->country_code)."'";
+			$sql .= " AND t.taux = ".((float) $tva_tx)." AND t.active = 1";
+			$sql .= " AND t.code = ''";
+			$resql = $db->query($sql);
+			if ($resql) {
+				$obj = $db->fetch_object($resql);
+				if ($obj) {
+					$npr = $obj->recuperableonly;
+					$localtax1 = $obj->localtax1;
+					$localtax2 = $obj->localtax2;
+					$localtax1_type = $obj->localtax1_type;
+					$localtax2_type = $obj->localtax2_type;
+				}
 			}
 		}
 
@@ -829,22 +948,23 @@ if (!empty($conf->global->PRODUIT_MULTIPRICES) || !empty($conf->global->PRODUIT_
 			}
 
 			// TVA
+			print '<!-- Default VAT Rate -->';
 			print '<tr><td class="titlefieldcreate">'.$langs->trans("DefaultTaxRate").'</td><td>';
 
 			$positiverates = '';
 			if (price2num($object->tva_tx)) {
-				$positiverates .= ($positiverates ? '/' : '').price2num($object->tva_tx);
+				$positiverates .= ($positiverates ? '<span class="opacitymedium">/</span>' : '').price2num($object->tva_tx);
 			}
 			if (price2num($object->localtax1_type)) {
-				$positiverates .= ($positiverates ? '/' : '').price2num($object->localtax1_tx);
+				$positiverates .= ($positiverates ? '<span class="opacitymedium">/</span>' : '').price2num($object->localtax1_tx);
 			}
 			if (price2num($object->localtax2_type)) {
-				$positiverates .= ($positiverates ? '/' : '').price2num($object->localtax2_tx);
+				$positiverates .= ($positiverates ? '<span class="opacitymedium">/</span>' : '').price2num($object->localtax2_tx);
 			}
 			if (empty($positiverates)) {
 				$positiverates = '0';
 			}
-			echo vatrate($positiverates.($object->default_vat_code ? ' ('.$object->default_vat_code.')' : ''), '%', $object->tva_npr);
+			print vatrate($positiverates.($object->default_vat_code ? ' ('.$object->default_vat_code.')' : ''), true, $object->tva_npr, 1);
 			/*
 			if ($object->default_vat_code)
 			{

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

@@ -47,7 +47,6 @@ if (!$user->rights->projet->lire) {
 
 $hookmanager->initHooks(array('projettasknote'));
 
-//$result = restrictedArea($user, 'projet', $id, '', 'task'); // TODO ameliorer la verification
 
 $object = new Task($db);
 $projectstatic = new Project($db);
@@ -89,6 +88,7 @@ if ($id > 0 || $ref) {
 	$object->fetch($id, $ref);
 }
 
+//$result = restrictedArea($user, 'projet', $id, '', 'task'); // TODO ameliorer la verification
 restrictedArea($user, 'projet', $object->fk_project, 'projet&project');
 
 $permissionnote = ($user->rights->projet->creer || $user->rights->projet->all->creer);

+ 0 - 2
htdocs/public/onlinesign/newonlinesign.php

@@ -448,10 +448,8 @@ if ($action == "dosign" && empty($cancel)) {
 	  $("#clearsignature").on("click",function(){
 		$("#signature").jSignature("clear");
 		$("#signbutton").attr("disabled",true);
-		/* $("#clearsignature").css("display","none"); */
 	  });
 
-	  /* $("#clearsignature").css("display","none"); */
 	  $("#signbutton").attr("disabled",true);
 	});
 	</script>';

+ 1 - 1
htdocs/public/ticket/create_ticket.php

@@ -173,7 +173,7 @@ if (empty($reshook) && $action == 'create_ticket' && GETPOST('save', 'alpha')) {
 	// Check Captcha code if is enabled
 	if (!empty($conf->global->MAIN_SECURITY_ENABLECAPTCHA)) {
 		$sessionkey = 'dol_antispam_value';
-		$ok = (array_key_exists($sessionkey, $_SESSION) === true && (strtolower($_SESSION[$sessionkey]) === strtolower(GETPOST('code', 'none'))));
+		$ok = (array_key_exists($sessionkey, $_SESSION) === true && (strtolower($_SESSION[$sessionkey]) === strtolower(GETPOST('code', 'restricthtml'))));
 		if (!$ok) {
 			$error++;
 			array_push($object->errors, $langs->trans("ErrorBadValueForCode"));

+ 1 - 1
htdocs/salaries/card.php

@@ -277,7 +277,7 @@ if ($action == 'add' && empty($cancel)) {
 			$paiement->amounts      = array($object->id=>$amount); // Tableau de montant
 			$paiement->paiementtype = $type_payment;
 			$paiement->num_payment  = GETPOST("num_payment", 'alphanohtml');
-			$paiement->note = GETPOST("note", 'none');
+			$paiement->note = GETPOST("note", 'restricthtml');
 
 			if (!$error) {
 				$paymentid = $paiement->create($user, (int) GETPOST('closepaidsalary'));

+ 2 - 2
htdocs/salaries/paiement_salary.php

@@ -110,8 +110,8 @@ if ($action == 'add_payment' || ($action == 'confirm_paiement' && $confirm == 'y
 			$paiement->amounts      = $amounts; // Tableau de montant
 			$paiement->paiementtype = GETPOST("paiementtype", 'alphanohtml');
 			$paiement->num_payment  = GETPOST("num_payment", 'alphanohtml');
-			$paiement->note         = GETPOST("note", 'none');
-			$paiement->note_private = GETPOST("note", 'none');
+			$paiement->note         = GETPOST("note", 'restricthtml');
+			$paiement->note_private = GETPOST("note", 'restricthtml');
 
 			if (!$error) {
 				$paymentid = $paiement->create($user, (GETPOST('closepaidsalary') == 'on' ? 1 : 0));

+ 20 - 5
htdocs/theme/eldy/global.inc.php

@@ -1888,7 +1888,7 @@ div.blockvmenulogo
 	margin-left: 11px;
 	margin-right: 9px;
 	padding: 0;
-	height: <?php echo $disableimages ? '20' : '35'; ?>px;
+	height: <?php echo $disableimages ? '18' : '35'; ?>px;
 	/* width: 100px; */
 	max-width: 100px;
 	vertical-align: middle;
@@ -4558,7 +4558,7 @@ label.radioprivate {
 /*	margin-bottom: 2px;
 	margin-top: 2px; */
 }
-div.divphotoref > a > .photowithmargin {		/* Margin right for photo not inside a div.photoref frame only */
+div.divphotoref > img.photowithmargin, div.divphotoref > a > .photowithmargin {		/* Margin right for photo not inside a div.photoref frame only */
 	margin-right: 15px;
 }
 .photowithborder {
@@ -4936,6 +4936,11 @@ tr.visible {
 /*  Module website                                                                */
 /* ============================================================================== */
 
+.websiteformtoolbar {
+	position: sticky;
+	top: <?php echo $disableimages ? '32px' : '52px'; ?>;
+}
+
 .exampleapachesetup {
 	overflow-y: auto;
 	height: 100px;
@@ -4988,7 +4993,13 @@ span[phptag] {
 	/* display: inline-block; */
 	padding-<?php echo $right; ?>: 10px;
 	vertical-align: middle;
-	/* line-height: 28px; */
+	line-height: 28px;
+}
+.websiteselectionsection {
+	font-size: 0.85em;
+}
+.websiteselection span {
+	vertical-align: middle;
 }
 .websitetools {
 	float: right;
@@ -5000,7 +5011,7 @@ span[phptag] {
 }
 .websiteinputurl {
 	display: inline-block;
-	vertical-align: top;
+	vertical-align: middle;
 	line-height: 28px;
 }
 .websiteiframenoborder {
@@ -5035,6 +5046,10 @@ span.websitebuttonsitepreviewdisabled img, a.websitebuttonsitepreviewdisabled im
 	line-height: 1em;
 }
 
+.websitebar input.bordertransp {
+	line-height: normal !important;
+}
+
 #divbodywebsite section p {
 	margin: unset;
 }
@@ -7340,7 +7355,7 @@ div.clipboardCPValue.hidewithsize {
 
 	.websiteselectionsection {
 		border-left: unset;
-		boerder-right: unset;
+		border-right: unset;
 		padding-left: 5px;
 	}
 

+ 1 - 0
htdocs/theme/md/btn.inc.php

@@ -234,6 +234,7 @@ span.butActionNewRefused>span.fa, span.butActionNewRefused>span.fa:hover
 	background: var(--butactiondeletebg);
 	/* border: 1px solid #633; */
 	color: #633;
+	vertical-align: middle;
 }
 
 .butActionDelete:hover {

+ 1 - 1
htdocs/theme/md/main_menu_fa_icons.inc.php

@@ -11,7 +11,7 @@
 	font-variant: normal;
 	text-rendering: auto;
 	line-height: 26px;
-	font-size: 1.5em;
+	font-size: 1.3em;
 	-webkit-font-smoothing: antialiased;
 	text-align:center;
 	text-decoration:none;

+ 14 - 6
htdocs/theme/md/style.css.php

@@ -3041,11 +3041,11 @@ div.blockvmenulogo
 	border-bottom: 0 !important;
 }
 .menulogocontainer {
-	margin: <?php echo $disableimages ? '0' : '6'; ?>px;
+	margin: <?php echo $disableimages ? '-1' : '6'; ?>px;
 	margin-left: 12px;
 	margin-right: 6px;
 	padding: 0;
-	height: <?php echo $disableimages ? '20' : '32'; ?>px;
+	height: <?php echo $disableimages ? '18' : '32'; ?>px;
 	/* width: 100px; */
 	max-width: 100px;
 	vertical-align: middle;
@@ -4492,7 +4492,7 @@ label.radioprivate {
 /*	margin-bottom: 2px;
 	margin-top: 2px; */
 }
-div.divphotoref > a > .photowithmargin {		/* Margin right for photo not inside a div.photoref frame only */
+div.divphotoref > img.photowithmargin, div.divphotoref > a > .photowithmargin {		/* Margin right for photo not inside a div.photoref frame only */
 	margin-right: 15px;
 }
 .photowithborder {
@@ -4862,6 +4862,11 @@ tr.visible {
 /*  Module website                                                                */
 /* ============================================================================== */
 
+.websiteformtoolbar {
+	position: sticky;
+	top: <?php echo $disableimages ? '36px' : '50px'; ?>;
+}
+
 .exampleapachesetup {
 	overflow-y: auto;
 	max-height: 100px;
@@ -4891,7 +4896,10 @@ span[phptag] {
 	border-bottom: 1px solid #ccc;
 	background: #eee;
 	display: inline-block;
-	padding: 4px 0 4px 0;
+	padding: 5px 5px 5px 5px;
+}
+.centpercent.websitebar {
+	width: calc(100% - 10px);
 }
 .websitebar .buttonDelete, .websitebar .button {
 	text-shadow: none;
@@ -4908,7 +4916,7 @@ span[phptag] {
 }
 .websiteselection {
 	/* display: inline-block; */
-	padding-left: 10px;
+	padding-<?php echo $right; ?>: 10px;
 	vertical-align: middle;
 }
 .websitetools {
@@ -7126,7 +7134,7 @@ div.clipboardCPValue.hidewithsize {
 
 	.websiteselectionsection {
 		border-left: unset;
-		boerder-right: unset;
+		border-right: unset;
 		padding-left: 5px;
 	}
 

+ 1 - 1
htdocs/user/card.php

@@ -404,7 +404,7 @@ if (empty($reshook)) {
 				$object->firstname = GETPOST("firstname", 'alphanohtml');
 				$object->login = GETPOST("login", 'alphanohtml');
 				$object->gender = GETPOST("gender", 'aZ09');
-				$object->pass = GETPOST("password", 'none');
+				$object->pass = GETPOST("password", 'none');	// We can keep 'none' for password fields
 				$object->api_key = (GETPOST("api_key", 'alphanohtml')) ? GETPOST("api_key", 'alphanohtml') : $object->api_key;
 				if (!empty($user->admin)) {
 					$object->admin = GETPOST("admin", "int"); // admin flag can only be set/unset by an admin user. A test is also done later when forging sql request

+ 1 - 1
htdocs/viewimage.php

@@ -290,7 +290,7 @@ if ($modulepart == 'barcode') {
 	if (in_array($encoding, array('EAN8', 'EAN13'))) {
 		$code = GETPOST("code", 'alphanohtml');
 	} else {
-		$code = GETPOST("code", 'none'); // This can be rich content (qrcode, datamatrix, ...)
+		$code = GETPOST("code", 'restricthtml'); // This can be rich content (qrcode, datamatrix, ...)
 	}
 
 	if (empty($generator) || empty($encoding)) {

+ 21 - 15
htdocs/website/index.php

@@ -2508,7 +2508,7 @@ llxHeader($moreheadcss.$moreheadjs, $langs->trans("WebsiteSetup"), $helpurl, '',
 
 print "\n";
 print '<!-- Open form for all page -->'."\n";
-print '<form action="'.$_SERVER["PHP_SELF"].($action == 'file_manager' ? '?uploadform=1': '').'" method="POST" enctype="multipart/form-data">';
+print '<form action="'.$_SERVER["PHP_SELF"].($action == 'file_manager' ? '?uploadform=1': '').'" method="POST" enctype="multipart/form-data" class="websiteformtoolbar">';
 print '<input type="hidden" name="token" value="'.newToken().'">';
 print '<input type="hidden" name="backtopage" value="'.$backtopage.'">';
 
@@ -2669,7 +2669,7 @@ if (!GETPOST('hide_websitemenu')) {
 			print ' &nbsp; ';
 
 			//print '<input type="submit" class="button bordertransp"'.$disabled.' value="'.dol_escape_htmltag($langs->trans("EditCss")).'" name="editcss">';
-			print '<a href="'.$_SERVER["PHP_SELF"].'?website='.$object->ref.'&pageid='.$pageid.'&action=editcss&token='.newToken().'" class="button bordertransp"'.$disabled.'>'.dol_escape_htmltag($langs->trans($conf->dol_optimize_smallscreen ? "Properties" : "EditCss")).'</a>';
+			print '<a href="'.$_SERVER["PHP_SELF"].'?website='.$object->ref.'&pageid='.$pageid.'&action=editcss&token='.newToken().'" class="button bordertransp" title="'.dol_escape_htmltag($langs->trans("EditCss")).'"'.$disabled.'><span class="fa fa-cog paddingrightonly"></span><span class="hideonsmartphone">'.dol_escape_htmltag($langs->trans("EditCss")).'</span></a>';
 
 			$importlabel = $langs->trans("ImportSite");
 			$exportlabel = $langs->trans("ExportSite");
@@ -2692,24 +2692,24 @@ if (!GETPOST('hide_websitemenu')) {
 			print '<input type="submit" class="buttonDelete bordertransp" name="deletesite" value="'.$langs->trans("Delete").'"'.($atleastonepage ? ' disabled="disabled"' : '').'>';
 
 			// Regenerate all pages
-			print '<a href="'.$_SERVER["PHP_SELF"].'?action=regeneratesite&website='.$website->ref.'" class="button bordertransp"'.$disabled.' title="'.dol_escape_htmltag($langs->trans("RegenerateWebsiteContent")).'"><span class="fa fa-cogs"><span></a>';
+			print '<a href="'.$_SERVER["PHP_SELF"].'?action=regeneratesite&website='.$website->ref.'" class="button bordertransp"'.$disabled.' title="'.dol_escape_htmltag($langs->trans("RegenerateWebsiteContent")).'"><span class="far fa-hdd"></span></a>';
 
 			// Generate site map
-			print '<a href="'.$_SERVER["PHP_SELF"].'?action=confirmgeneratesitemaps&website='.$website->ref.'" class="button bordertransp"'.$disabled.' title="'.dol_escape_htmltag($langs->trans("GenerateSitemaps")).'"><span class="fa fa-sitemap"><span></a>';
+			print '<a href="'.$_SERVER["PHP_SELF"].'?action=confirmgeneratesitemaps&website='.$website->ref.'" class="button bordertransp"'.$disabled.' title="'.dol_escape_htmltag($langs->trans("GenerateSitemaps")).'"><span class="fa fa-sitemap"></span></a>';
 
-			print '<a href="'.$_SERVER["PHP_SELF"].'?action=replacesite&website='.$website->ref.'" class="button bordertransp"'.$disabled.' title="'.dol_escape_htmltag($langs->trans("ReplaceWebsiteContent")).'"><span class="fa fa-search"><span></a>';
+			print '<a href="'.$_SERVER["PHP_SELF"].'?action=replacesite&website='.$website->ref.'" class="button bordertransp"'.$disabled.' title="'.dol_escape_htmltag($langs->trans("ReplaceWebsiteContent")).'"><span class="fa fa-search"></span></a>';
 		}
 
 		print '</span>';
 
 		if ($websitekey && $websitekey != '-1' && ($action == 'preview' || $action == 'createfromclone' || $action == 'createpagefromclone' || $action == 'deletesite')) {
 			print '<span class="websiteselection">';
-			//print '<a href="'.$_SERVER["PHP_SELF"].'?action=file_manager&website='.$website->ref.'" class="button bordertransp"'.$disabled.' title="'.dol_escape_htmltag($langs->trans("MediaFiles")).'"><span class="fa fa-image"><span></a>';
-			print dolButtonToOpenUrlInDialogPopup('file_manager', $langs->transnoentitiesnoconv("MediaFiles"), '<span class="fa fa-image"><span>', '/website/index.php?action=file_manager&website='.$website->ref, $disabled);
+			//print '<a href="'.$_SERVER["PHP_SELF"].'?action=file_manager&website='.$website->ref.'" class="button bordertransp"'.$disabled.' title="'.dol_escape_htmltag($langs->trans("MediaFiles")).'"><span class="fa fa-image"></span></a>';
+			print dolButtonToOpenUrlInDialogPopup('file_manager', $langs->transnoentitiesnoconv("MediaFiles"), '<span class="fa fa-image"></span>', '/website/index.php?action=file_manager&website='.$website->ref, $disabled);
 
 			if (!empty($conf->categorie->enabled)) {
-				//print '<a href="'.DOL_URL_ROOT.'/categories/index.php?leftmenu=website&dol_hide_leftmenu=1&nosearch=1&type=website_page&website='.$website->ref.'" class="button bordertransp"'.$disabled.' title="'.dol_escape_htmltag($langs->trans("Categories")).'"><span class="fa fa-tags"><span></a>';
-				print dolButtonToOpenUrlInDialogPopup('categories', $langs->transnoentitiesnoconv("Categories"), '<span class="fa fa-tags"><span>', '/categories/index.php?leftmenu=website&nosearch=1&type=website_page&website='.$website->ref, $disabled);
+				//print '<a href="'.DOL_URL_ROOT.'/categories/index.php?leftmenu=website&dol_hide_leftmenu=1&nosearch=1&type=website_page&website='.$website->ref.'" class="button bordertransp"'.$disabled.' title="'.dol_escape_htmltag($langs->trans("Categories")).'"><span class="fa fa-tags"></span></a>';
+				print dolButtonToOpenUrlInDialogPopup('categories', $langs->transnoentitiesnoconv("Categories"), '<span class="fa fa-tags"></span>', '/categories/index.php?leftmenu=website&nosearch=1&type=website_page&website='.$website->ref, $disabled);
 			}
 
 			print '</span>';
@@ -2808,7 +2808,7 @@ if (!GETPOST('hide_websitemenu')) {
 		print '</span>';
 
 		print '<span class="websiteselection hideonsmartphoneimp">';
-		print '<a href="'.$_SERVER["PHP_SELF"].'?action=createcontainer&website='.$website->ref.'" class="button bordertransp"'.$disabled.' title="'.dol_escape_htmltag($langs->trans("AddPage")).'"><span class="fa fa-plus-circle valignmiddle btnTitle-icon"><span></a>';
+		print '<a href="'.$_SERVER["PHP_SELF"].'?action=createcontainer&website='.$website->ref.'" class="button bordertransp"'.$disabled.' title="'.dol_escape_htmltag($langs->trans("AddPage")).'"><span class="fa fa-plus-circle valignmiddle btnTitle-icon"></span></a>';
 		print '</span>';
 
 		//print '<span class="websiteselection">';
@@ -2846,9 +2846,10 @@ if (!GETPOST('hide_websitemenu')) {
 			print $langs->trans("New");
 		}
 
-		//print '<input type="submit" class="button" name="refreshpage" value="'.$langs->trans("Load").'"'.($atleastonepage?'':' disabled="disabled"').'>';
-		print '<input type="image" class="valignmiddle" src="'.img_picto('', 'refresh', '', 0, 1).'" name="refreshpage" value="'.$langs->trans("Load").'"'.(($atleastonepage && $action != 'editsource') ? '' : ' disabled="disabled"').'>';
 
+		print '<span class="websiteselection">';
+
+		print '<input type="image" class="valignmiddle" src="'.img_picto('', 'refresh', '', 0, 1).'" name="refreshpage" value="'.$langs->trans("Load").'"'.(($atleastonepage && $action != 'editsource') ? '' : ' disabled="disabled"').'>';
 
 		// Print nav arrows
 		$pagepreviousid = 0;
@@ -2887,6 +2888,8 @@ if (!GETPOST('hide_websitemenu')) {
 			print '<span class="valignmiddle opacitymedium">'.img_next($langs->trans("Next")).'</span>';
 		}
 
+		print '</span>';
+
 		$websitepage = new WebSitePage($db);
 		if ($pageid > 0 && ($action == 'preview' || $action == 'createfromclone' || $action == 'createpagefromclone')) {
 			$websitepage->fetch($pageid);
@@ -2962,13 +2965,16 @@ if (!GETPOST('hide_websitemenu')) {
 					print $formconfirm;
 				}
 
+				print '<span class="websiteselection">';
+
 				print ' &nbsp; ';
 
 				//print '<input type="submit" class="button bordertransp"'.$disabled.' value="'.dol_escape_htmltag($langs->trans("EditPageMeta")).'" name="editmeta">';
-				print '<a href="'.$_SERVER["PHP_SELF"].'?website='.$object->ref.'&pageid='.$pageid.'&action=editmeta&token='.newToken().'" class="button bordertransp"'.$disabled.'>'.dol_escape_htmltag($langs->trans($conf->dol_optimize_smallscreen ? "Properties" : "EditPageMeta")).'</a>';
+				print '<a href="'.$_SERVER["PHP_SELF"].'?website='.$object->ref.'&pageid='.$pageid.'&action=editmeta&token='.newToken().'" class="button bordertransp" title="'.dol_escape_htmltag($langs->trans("EditPageMeta")).'"'.$disabled.'><span class="fa fa-cog paddingrightonly"></span><span class="hideonsmartphone">'.dol_escape_htmltag($langs->trans("EditPageMeta")).'</span></a>';
 
 				//print '<input type="submit" class="button bordertransp"'.$disabled.' value="'.dol_escape_htmltag($langs->trans("EditHTMLSource")).'" name="editsource">';
 				print '<a href="'.$_SERVER["PHP_SELF"].'?website='.$object->ref.'&pageid='.$pageid.'&action=editsource&token='.newToken().'" class="button bordertransp"'.$disabled.'>'.dol_escape_htmltag($langs->trans($conf->dol_optimize_smallscreen ? "HTML" : "EditHTMLSource")).'</a>';
+				print '</span>';
 
 				print '<!-- button EditInLine and ShowSubcontainers -->'."\n";
 				print '<div class="websiteselectionsection inline-block">';
@@ -3055,11 +3061,11 @@ if (!GETPOST('hide_websitemenu')) {
 					//$disabled=' disabled="disabled"';
 					//print '<span class="button bordertransp disabled"'.$disabled.' title="'.dol_escape_htmltag($langs->trans("SetAsHomePage")).'"><span class="fa fa-home"></span></span>';
 					//print '<input type="submit" class="button bordertransp" disabled="disabled" value="'.dol_escape_htmltag($langs->trans("SetAsHomePage")).'" name="setashome">';
-					print '<a href="#" class="button bordertransp disabled" disabled="disabled" title="'.dol_escape_htmltag($langs->trans("SetAsHomePage")).'"><span class="fa fa-home valignmiddle btnTitle-icon"><span></a>';
+					print '<a href="#" class="button bordertransp disabled" disabled="disabled" title="'.dol_escape_htmltag($langs->trans("SetAsHomePage")).'"><span class="fa fa-home valignmiddle btnTitle-icon"></span></a>';
 				} else {
 					//$disabled='';
 					//print '<input type="submit" class="button bordertransp"'.$disabled.' value="'.dol_escape_htmltag($langs->trans("SetAsHomePage")).'" name="setashome">';
-					print '<a href="'.$_SERVER["PHP_SELF"].'?action=setashome&token='.newToken().'&website='.$website->ref.'&pageid='.$pageid.'" class="button bordertransp"'.$disabled.' title="'.dol_escape_htmltag($langs->trans("SetAsHomePage")).'"><span class="fa fa-home valignmiddle btnTitle-icon"><span></a>';
+					print '<a href="'.$_SERVER["PHP_SELF"].'?action=setashome&token='.newToken().'&website='.$website->ref.'&pageid='.$pageid.'" class="button bordertransp"'.$disabled.' title="'.dol_escape_htmltag($langs->trans("SetAsHomePage")).'"><span class="fa fa-home valignmiddle btnTitle-icon"></span></a>';
 				}
 				print '<input type="submit" class="button bordertransp"'.$disabled.' value="'.dol_escape_htmltag($langs->trans("ClonePage")).'" name="createpagefromclone">';
 				print '<input type="submit" class="buttonDelete bordertransp" name="delete" value="'.$langs->trans("Delete").'"'.($atleastonepage ? '' : ' disabled="disabled"').'>';

+ 19 - 0
test/phpunit/CodingPhpTest.php

@@ -437,6 +437,25 @@ class CodingPhpTest extends PHPUnit\Framework\TestCase
 			$this->assertTrue($ok, 'Found a $_SERVER[\'QUERY_STRING\'] without dol_escape_htmltag neither dol_string_nohtmltag around it, in file '.$file['relativename'].'. Bad.');
 
 
+			// Check GETPOST(... 'none');
+			$ok=true;
+			$matches=array();
+			preg_match_all('/GETPOST\s*\(([^\)]+),\s*["\']none["\']/i', $filecontent, $matches, PREG_SET_ORDER);
+			foreach ($matches as $key => $val) {
+				var_dump($val);
+				if (!in_array($val[1], array(
+						"'replacestring'", "'htmlheader'", "'WEBSITE_HTML_HEADER'", "'WEBSITE_CSS_INLINE'", "'WEBSITE_JS_INLINE'", "'WEBSITE_MANIFEST_JSON'", "'PAGE_CONTENT'", "'WEBSITE_README'",
+						"'search_status'", '"mysqldump"', '"postgresqldump"', "'db_pass_root'", "'db_pass'", '"pass"', '"pass1"', '"pass2"', '"password"', "'password'", '"MAIN_MAIL_SMTPS_PW"'))) {
+					$ok=false;
+					break;
+				}
+				//if ($reg[0] != 'db') $ok=false;
+			}
+			//print __METHOD__." Result for checking we don't have non escaped string in sql requests for file ".$file."\n";
+			$this->assertTrue($ok, 'Found a GETPOST that use \'none\' as a parameter in file '.$file['relativename'].' and param is not an allowed parameter for using none - Bad.');
+			//exit;
+
+
 			// Test that first param of print_liste_field_titre is a translation key and not the translated value
 			$ok=true;
 			$matches=array();

+ 5 - 0
test/phpunit/SecurityTest.php

@@ -791,6 +791,11 @@ class SecurityTest extends PHPUnit\Framework\TestCase
 		 $this->assertEquals(400, $tmp['http_code'], 'Should GET url to '.$url.' that resolves to a local URL');	// Test we receive an error because localtest.me is not an external URL
 		 */
 
+		$url = 'http://192.0.0.192';
+		$tmp = getURLContent($url, 'GET', '', 0, array(), array('http', 'https'), 0);		// Only external URL but on an IP in blacklist
+		print __METHOD__." url=".$url." tmp['http_code'] = ".$tmp['http_code']."\n";
+		$this->assertEquals(400, $tmp['http_code'], 'Access should be refused and was not');	// Test we receive an error because ip is in blacklist
+
 		return 0;
 	}