From c2f91b2e15cec9f680041aef260d348a2b2213cd Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Fri, 3 Feb 2023 14:02:46 +0100 Subject: [PATCH 01/35] FIX attach file and send by mail in ticket --- htdocs/core/class/html.formticket.class.php | 8 ++++---- htdocs/ticket/class/ticket.class.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/htdocs/core/class/html.formticket.class.php b/htdocs/core/class/html.formticket.class.php index cea007f8e61..cd6b9ecd7fc 100644 --- a/htdocs/core/class/html.formticket.class.php +++ b/htdocs/core/class/html.formticket.class.php @@ -910,7 +910,7 @@ class FormTicket $langs->loadLangs(array('other', 'mails')); // Clear temp files. Must be done at beginning, before call of triggers - if (GETPOST('mode', 'alpha') == 'init' || (GETPOST('modelmailselected', 'alpha') && GETPOST('modelmailselected', 'alpha') != '-1')) { + if (GETPOST('mode', 'alpha') == 'init' || (GETPOST('modelselected') && GETPOST('modelmailselected', 'alpha') && GETPOST('modelmailselected', 'alpha') != '-1')) { $this->clear_attached_files(); } @@ -943,10 +943,10 @@ class FormTicket $listofmimes = array(); $keytoavoidconflict = empty($this->trackid) ? '' : '-'.$this->trackid; // this->trackid must be defined - if (GETPOST('mode', 'alpha') == 'init' || (GETPOST('modelmailselected', 'alpha') && GETPOST('modelmailselected', 'alpha') != '-1')) { - if (!empty($arraydefaultmessage->joinfiles) && is_array($this->param['fileinit'])) { + if (GETPOST('mode', 'alpha') == 'init' || (GETPOST('modelselected') && GETPOST('modelmailselected', 'alpha') && GETPOST('modelmailselected', 'alpha') != '-1')) { + if (!empty($arraydefaultmessage->joinfiles) && !empty($this->param['fileinit']) && is_array($this->param['fileinit'])) { foreach ($this->param['fileinit'] as $file) { - $this->add_attached_files($file, basename($file), dol_mimetype($file)); + $formmail->add_attached_files($file, basename($file), dol_mimetype($file)); } } } diff --git a/htdocs/ticket/class/ticket.class.php b/htdocs/ticket/class/ticket.class.php index e03bdabe617..d12df619bb0 100644 --- a/htdocs/ticket/class/ticket.class.php +++ b/htdocs/ticket/class/ticket.class.php @@ -2454,7 +2454,7 @@ class Ticket extends CommonObject $maxheightmini = 72; $formmail = new FormMail($this->db); - + $formmail->trackid = 'tic'.$this->id; $attachedfiles = $formmail->get_attached_files(); $filepath = $attachedfiles['paths']; From 394c3f847c4ffb1fb62a08d2d3f45450fb387797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20David?= Date: Mon, 6 Feb 2023 17:03:51 +0100 Subject: [PATCH 02/35] FIX clicktodial backtopage --- htdocs/core/lib/functions.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index f9adc7bcf40..dafa33242df 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -3410,7 +3410,7 @@ function dol_print_phone($phone, $countrycode = '', $cid = 0, $socid = 0, $addli $type = 'AC_FAX'; } if (!empty($conf->global->AGENDA_ADDACTIONFORPHONE)) { - $link = ''.img_object($langs->trans("AddAction"), "calendar").''; + $link = ''.img_object($langs->trans("AddAction"), "calendar").''; } if ($link) { $newphone = '
'.$newphone.' '.$link.'
'; From fc767cbfbef84278a5998dd0d08c649133d01eea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Fali=C3=A8re?= Date: Mon, 6 Feb 2023 17:56:21 +0100 Subject: [PATCH 03/35] FIX discount wasn't taken into account when adding a line in BOM --- htdocs/bom/class/bom.class.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/htdocs/bom/class/bom.class.php b/htdocs/bom/class/bom.class.php index 0e57bebd922..e414954d1a5 100644 --- a/htdocs/bom/class/bom.class.php +++ b/htdocs/bom/class/bom.class.php @@ -1058,8 +1058,10 @@ class BOM extends CommonObject return -1; } $line->unit_cost = price2num((!empty($tmpproduct->cost_price)) ? $tmpproduct->cost_price : $tmpproduct->pmp); - if (empty($line->unit_cost)) { - if ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0) { + if ((empty($line->unit_cost)) && ($productFournisseur->find_min_price_product_fournisseur($line->fk_product) > 0)) { + if ($productFournisseur->fourn_remise_percent != "0") { + $line->unit_cost = $productFournisseur->fourn_unitprice_with_discount; + } else { $line->unit_cost = $productFournisseur->fourn_unitprice; } } From 016d1117a69f3818485b3b7ad8e8c0d4a321fa7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Fali=C3=A8re?= Date: Mon, 6 Feb 2023 18:02:28 +0100 Subject: [PATCH 04/35] added my name to the file --- htdocs/bom/class/bom.class.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/htdocs/bom/class/bom.class.php b/htdocs/bom/class/bom.class.php index e414954d1a5..8b866e221de 100644 --- a/htdocs/bom/class/bom.class.php +++ b/htdocs/bom/class/bom.class.php @@ -1,5 +1,6 @@ +/* Copyright (C) 2019 Laurent Destailleur + * Copyright (C) 2023 Benjamin Falière * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From bce55fc3b4252c5f012df34700c21e18c02120a3 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 7 Feb 2023 01:40:20 +0100 Subject: [PATCH 05/35] Trans --- htdocs/langs/en_US/admin.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index 28d2c7593fc..009dd47dbb5 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -451,7 +451,7 @@ ExtrafieldCheckBox=Checkboxes ExtrafieldCheckBoxFromList=Checkboxes from table ExtrafieldLink=Link to an object ComputedFormula=Computed field -ComputedFormulaDesc=You can enter here a formula using other properties of object or any PHP coding to get a dynamic computed value. You can use any PHP compatible formulas including the "?" condition operator, and following global object: $db, $conf, $langs, $mysoc, $user, $object.
WARNING: Only some properties of $object may be available. If you need a properties not loaded, just fetch yourself the object into your formula like in the second example.
Using a computed field means you can't enter yourself any value from interface. Also, if there is a syntax error, the formula may return nothing.

Example of formula:
$object->id < 10 ? round($object->id / 2, 2): ($object->id + 2 * $user->id) * (int) substr($mysoc->zip, 1, 2)

Example to reload object
(($reloadedobj = new Societe($db)) && ($reloadedobj->fetchNoCompute($obj->id ? $obj->id: ($obj->rowid ? $obj->rowid: $object->id)) > 0)) ? $reloadedobj->array_options['options_extrafieldkey'] * $reloadedobj->capital / 5: '-1'

Other example of formula to force load of object and its parent object:
(($reloadedobj = new Task($db)) && ($reloadedobj->fetchNoCompute($object->id) > 0) && ($secondloadedobj = new Project($db)) && ($secondloadedobj->fetchNoCompute($reloadedobj->fk_project) > 0)) ? $secondloadedobj->ref: 'Parent project not found' +ComputedFormulaDesc=You can enter here a formula using other properties of object or any PHP coding to get a dynamic computed value. You can use any PHP compatible formulas including the "?" condition operator, and following global object: $db, $conf, $langs, $mysoc, $user, $objectoffield.
WARNING: If you need properties of an object not loaded, just fetch yourself the object into your formula like in the second example.
Using a computed field means you can't enter yourself any value from interface. Also, if there is a syntax error, the formula may return nothing.

Example of formula:
$objectoffield->id < 10 ? round($objectoffield->id / 2, 2): ($objectoffield->id + 2 * $user->id) * (int) substr($mysoc->zip, 1, 2)

Example to reload object
(($reloadedobj = new Societe($db)) && ($reloadedobj->fetchNoCompute($objectoffield->id) > 0 ? $reloadedobj->array_options['options_extrafieldkey'] * $reloadedobj->capital / 5: '-1')

Other example of formula to force load of object and its parent object:
(($reloadedobj = new Task($db)) && ($reloadedobj->fetchNoCompute($objectoffield->id) > 0) && ($secondloadedobj = new Project($db)) && ($secondloadedobj->fetchNoCompute($reloadedobj->fk_project) > 0)) ? $secondloadedobj->ref: 'Parent project not found' Computedpersistent=Store computed field ComputedpersistentDesc=Computed extra fields will be stored in the database, however, the value will only be recalculated when the object of this field is changed. If the computed field depends on other objects or global data this value might be wrong!! ExtrafieldParamHelpPassword=Leaving this field blank means this value will be stored without encryption (field must be only hidden with star on screen).
Set 'auto' to use the default encryption rule to save password into database (then value read will be the hash only, no way to retrieve original value) From 7553a99fcb2d2469848ca1175054081a91fb1822 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 7 Feb 2023 01:43:38 +0100 Subject: [PATCH 06/35] Doc --- ChangeLog | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4da3132248c..1b877e2b4b4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,15 +3,6 @@ English Dolibarr ChangeLog -------------------------------------------------------------- -***** ChangeLog for 18.0.0 compared to 17.0.0 ***** - -WARNING: - -Following changes may create regressions for some external modules, but were necessary to make Dolibarr better: -* The deprecated method escapeunderscore() of database handlers has been removed. You must use escapeforlike instead. - - - ***** ChangeLog for 17.0.0 compared to 16.0.0 ***** @@ -218,7 +209,8 @@ Following changes may create regressions for some external modules, but were nec * Tables llx_prelevement_facture and llx_prelevement_facture_demande have been renamed into llx_prelevement and llx_prelevement_demande. * Rename MAIN_LIST_ALLOW_NOTES into MAIN_LIST_HIDE_NOTES and rename MAIN_LIST_ALLOW_PRIVATE_NOTES into MAIN_LIST_HIDE_PRIVATE_NOTES * Rename the substitution for "project label" instead of "project title" in substitution variables - +* You must use "$objectoffield" to manipulate the current object inside the formulare of computed custom extrafields instead of $obj/$object. + ***** ChangeLog for 16.0.4 compared to 16.0.3 ***** From 7c4858fdc3ae5d42db94b4a63582aa788666b115 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 7 Feb 2023 11:05:09 +0100 Subject: [PATCH 07/35] Add page with help for ingoing email setup --- htdocs/admin/mails_ingoing.php | 137 +++++++++++++++++++++++++++++++++ htdocs/core/lib/admin.lib.php | 5 ++ htdocs/langs/en_US/mails.lang | 1 + 3 files changed, 143 insertions(+) create mode 100644 htdocs/admin/mails_ingoing.php diff --git a/htdocs/admin/mails_ingoing.php b/htdocs/admin/mails_ingoing.php new file mode 100644 index 00000000000..010d933821e --- /dev/null +++ b/htdocs/admin/mails_ingoing.php @@ -0,0 +1,137 @@ + + * Copyright (C) 2009-2012 Regis Houssin + * Copyright (C) 2013 Juanjo Menent + * Copyright (C) 2016 Jonathan TISSEAU + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file htdocs/admin/mails_ingoing.php + * \brief Page to setup emails entry + */ + +// Load Dolibarr environment +require '../main.inc.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; + +// Load translation files required by the page +$langs->loadLangs(array("companies", "products", "admin", "mails", "other", "errors")); + +$action = GETPOST('action', 'aZ09'); +$cancel = GETPOST('cancel', 'aZ09'); + +$trackid = GETPOST('trackid'); + +if (!$user->admin) { + accessforbidden(); +} + + +/* + * Actions + */ + +if ($action == 'update' && !$cancel) { +} + + + +/* + * View + */ + +$form = new Form($db); + +$linuxlike = 1; +if (preg_match('/^win/i', PHP_OS)) { + $linuxlike = 0; +} +if (preg_match('/^mac/i', PHP_OS)) { + $linuxlike = 0; +} + + +//$wikihelp = 'EN:Setup_EMails|FR:Paramétrage_EMails|ES:Configuración_EMails'; +$wikihelp = ''; +llxHeader('', $langs->trans("Setup"), $wikihelp); + +print load_fiche_titre($langs->trans("EMailsSetup"), '', 'title_setup'); + +$head = email_admin_prepare_head(); + +// List of sending methods +$listofmethods = array(); +$listofmethods['mail'] = 'PHP mail function'; +$listofmethods['smtps'] = 'SMTP/SMTPS socket library'; +if (version_compare(phpversion(), '7.0', '>=')) { + $listofmethods['swiftmailer'] = 'Swift Mailer socket library'; +} + +// List of oauth services +$oauthservices = array(); + +foreach ($conf->global as $key => $val) { + if (!empty($val) && preg_match('/^OAUTH_.*_ID$/', $key)) { + $key = preg_replace('/^OAUTH_/', '', $key); + $key = preg_replace('/_ID$/', '', $key); + if (preg_match('/^.*-/', $key)) { + $name = preg_replace('/^.*-/', '', $key); + } else { + $name = $langs->trans("NoName"); + } + $provider = preg_replace('/-.*$/', '', $key); + $provider = ucfirst(strtolower($provider)); + + $oauthservices[$key] = $name." (".$provider.")"; + } +} + +print dol_get_fiche_head($head, 'common_ingoing', '', -1); + +print '
'; +print ''.$langs->trans("EMailsInGoingDesc", $langs->transnoentitiesnoconv("EmailCollector"))."
\n"; +print "

\n"; + +/* +print '
'; // You can use div-table-responsive-no-min if you dont need reserved height for your table +print ''; +print ''; + +print '
'; + +print '
'; // You can use div-table-responsive-no-min if you dont need reserved height for your table +print '
'.$langs->trans("Parameter").''.$langs->trans("Value").'
'; + +// SMTPS oauth service +if (in_array(getDolGlobalString('MAIN_MAIL_SENDMODE', 'mail'), array('smtps', 'swiftmailer')) && getDolGlobalString('MAIN_MAIL_SMTPS_AUTH_TYPE') === "XOAUTH2") { + $text = $oauthservices[$conf->global->MAIN_MAIL_SMTPS_OAUTH_SERVICE]; + if (empty($text)) { + $text = $langs->trans("Undefined").img_warning(); + } + print ''; +} + +print '
'.$langs->trans("MAIN_MAIL_SMTPS_OAUTH_SERVICE").''.$text.'
'; +print '
'; +*/ + +print dol_get_fiche_end(); + + +// End of page +llxFooter(); +$db->close(); diff --git a/htdocs/core/lib/admin.lib.php b/htdocs/core/lib/admin.lib.php index 71bcc082961..b3ee5dc259a 100644 --- a/htdocs/core/lib/admin.lib.php +++ b/htdocs/core/lib/admin.lib.php @@ -2054,6 +2054,11 @@ function email_admin_prepare_head() $head[$h][2] = 'templates'; $h++; + $head[$h][0] = DOL_URL_ROOT."/admin/mails_ingoing.php"; + $head[$h][1] = $langs->trans("InGoingEmailSetup", $langs->transnoentitiesnoconv("EMailing")); + $head[$h][2] = 'common_ingoing'; + $h++; + complete_head_from_modules($conf, $langs, null, $head, $h, 'email_admin', 'remove'); return $head; diff --git a/htdocs/langs/en_US/mails.lang b/htdocs/langs/en_US/mails.lang index f83637c9c40..a112de8ceb2 100644 --- a/htdocs/langs/en_US/mails.lang +++ b/htdocs/langs/en_US/mails.lang @@ -179,3 +179,4 @@ RecordCreatedByEmailCollector=Record created by the Email Collector %s from emai DefaultBlacklistMailingStatus=Default value for field '%s' when creating a new contact DefaultStatusEmptyMandatory=Empty but mandatory WarningLimitSendByDay=WARNING: The setup or contract of your instance limits your number of emails per day to %s. Trying to send more may result in having your instance slow down or suspended. Please contact your support if you need a higher quota. +EMailsInGoingDesc=Incoming emails are managed by the module %s. You must enable and configure it if you need to support ingoing emails. From c545b98a24deb842c72bc303d41b66a0cdfe059a Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 7 Feb 2023 11:39:29 +0100 Subject: [PATCH 08/35] The Use of PHP-IMAP lib must be enabled from interface --- htdocs/admin/emailcollector_list.php | 60 +++++++++++++++++----------- htdocs/langs/en_US/admin.lang | 1 + 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/htdocs/admin/emailcollector_list.php b/htdocs/admin/emailcollector_list.php index fb09143412f..de18b57b297 100644 --- a/htdocs/admin/emailcollector_list.php +++ b/htdocs/admin/emailcollector_list.php @@ -273,25 +273,6 @@ include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php'; $parameters = array(); $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object); // Note that $action and $object may have been modified by hook $sql .= $hookmanager->resPrint; - -/* If a group by is required -$sql.= " GROUP BY "; -foreach ($object->fields as $key => $val) { - $sql .= "t.".$db->escape($key).", "; -} -// Add fields from extrafields -if (!empty($extrafields->attributes[$object->table_element]['label'])) { - foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $val) { - $sql .= ($extrafields->attributes[$object->table_element]['type'][$key] != 'separate' ? "ef.".$key.', ' : ''); - } -} -// Add where from hooks -$parameters=array(); -$reshook = $hookmanager->executeHooks('printFieldListGroupBy', $parameters, $object); // Note that $action and $object may have been modified by hook -$sql .= $hookmanager->resPrint; -$sql = preg_replace('/,\s*$/', '', $sql); -*/ - // Count total nb of records $nbtotalofrecords = ''; if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST)) { @@ -333,6 +314,22 @@ if ($num == 1 && !empty($conf->global->MAIN_SEARCH_DIRECT_OPEN_IF_ONLY_ONE) && $ llxHeader('', $title, $help_url, '', 0, 0, $morejs, $morecss, '', ''); + +$linkback = ''.$langs->trans("BackToModuleList").''; +print load_fiche_titre($title, $linkback, 'title_setup'); + + +$head = array(); +$h = 0; +$head[$h][0] = DOL_URL_ROOT."/admin/emailcollector_list.php"; +$head[$h][1] = $langs->trans("Setup"); +$head[$h][2] = 'common'; +$h++; + +print dol_get_fiche_head($head, 'common', '', -1); + + + $arrayofselected = is_array($toselect) ? $toselect : array(); $param = ''; @@ -388,11 +385,9 @@ print ''; print ''; print ''; -$linkback = ''.$langs->trans("BackToModuleList").''; - $newcardbutton = dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', 'emailcollector_card.php?action=create&backtopage='.urlencode($_SERVER['PHP_SELF']), '', $permissiontoadd); -print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, 'email', 0, $newcardbutton.' '.$linkback, '', $limit, 0, 0, 1); +print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, 'email', 0, $newcardbutton, '', $limit, 0, 0, 1); // Add code for pre mass action (confirmation or email presend form) /*$topicmail=""; @@ -699,7 +694,22 @@ print ''.$langs->trans("Parameter").''; print ''; print "\n"; -// Hide e-mail headers from collected messages +// MAIN_IMAP_USE_PHPIMAP: Enable use of the PHP Imap library +print ''; +//print $form->textwithpicto($langs->trans("MAIN_IMAP_USE_PHPIMAP"), $langs->transnoentitiesnoconv("MAIN_IMAP_USE_PHPIMAPDesc")); +print $langs->trans("MAIN_IMAP_USE_PHPIMAP"); +print ''; +print ''; +if ($conf->use_javascript_ajax) { + print ajax_constantonoff('MAIN_IMAP_USE_PHPIMAP'); +} else { + $arrval = array('0' => $langs->trans("No"), '1' => $langs->trans("Yes")); + print $form->selectarray("MAIN_IMAP_USE_PHPIMAP", $arrval, $conf->global->MAIN_IMAP_USE_PHPIMAP); +} +print ''; +print ''; + +// MAIN_EMAILCOLLECTOR_MAIL_WITHOUT_HEADER: Hide e-mail headers from collected messages print ''.$form->textwithpicto($langs->trans("EmailCollectorHideMailHeaders"), $langs->transnoentitiesnoconv("EmailCollectorHideMailHeadersHelp")).''; print ''; if ($conf->use_javascript_ajax) { @@ -738,6 +748,10 @@ if (in_array('builddoc', $arrayofmassactions) && ($nbtotalofrecords === '' || $n print $formfile->showdocuments('massfilesarea_emailcollector', '', $filedir, $urlsource, 0, $delallowed, '', 1, 1, 0, 48, 1, $param, $title, '', '', '', null, $hidegeneratedfilelistifempty); } + +dol_get_fiche_end(); + + // End of page llxFooter(); $db->close(); diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index 009dd47dbb5..66210ef9d52 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -2348,3 +2348,4 @@ AllowExternalDownload=Allow external download (without login, using a shared lin DeadlineDayVATSubmission=Deadline day for vat submission on the next month MaxNumberOfAttachementOnForms=Max number of joinded files in a form IfDefinedUseAValueBeetween=If defined, use a value between %s and %s +MAIN_IMAP_USE_PHPIMAP=Use the PHP-IMAP library for IMAP instead of native PHP IMAP. This also allow the use of OAuth2 connection for IMAP. \ No newline at end of file From 2190dcc9f7e390d6e739fb3450dbd080c0f74c00 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 7 Feb 2023 13:40:28 +0100 Subject: [PATCH 09/35] Fix status of website --- htdocs/website/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/website/index.php b/htdocs/website/index.php index 3aa1b622f6e..3a625818ba4 100644 --- a/htdocs/website/index.php +++ b/htdocs/website/index.php @@ -2807,7 +2807,7 @@ if (!GETPOST('hide_websitemenu')) { $atleastonepage = (is_array($array) && count($array) > 0); $websitepage = new WebSitePage($db); - if ($pageid > 0 && ($action == 'preview' || $action == 'createfromclone' || $action == 'createpagefromclone')) { + if ($pageid > 0) { $websitepage->fetch($pageid); } From 8d1a84d56c8fece9c540247c7e2b3037f0193818 Mon Sep 17 00:00:00 2001 From: Philippe GRAND Date: Tue, 7 Feb 2023 14:56:38 +0100 Subject: [PATCH 10/35] fix : Warning: Undefined property: ExpenseReport:: in /home/httpd/vhosts/aflac.fr/domains/dev.aflac.fr/httpdocs/core/lib/pdf.lib.php on line 2289 --- .../expensereport/class/expensereport.class.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/htdocs/expensereport/class/expensereport.class.php b/htdocs/expensereport/class/expensereport.class.php index 2c4b4bec855..74d668f9d59 100644 --- a/htdocs/expensereport/class/expensereport.class.php +++ b/htdocs/expensereport/class/expensereport.class.php @@ -133,6 +133,21 @@ class ExpenseReport extends CommonObject public $statuts_short = array(); public $statuts_logo; + // Multicurrency + /** + * @var int Currency ID + */ + public $fk_multicurrency; + + /** + * @var string multicurrency code + */ + public $multicurrency_code; + public $multicurrency_tx; + public $multicurrency_total_ht; + public $multicurrency_total_tva; + public $multicurrency_total_ttc; + /** * Draft status From 495a8656522df4c752c4f48ddbf5ed39d268889b Mon Sep 17 00:00:00 2001 From: Philippe GRAND Date: Tue, 7 Feb 2023 14:59:02 +0100 Subject: [PATCH 11/35] fix : Warning: Undefined property: ExpenseReportLine:: in /home/httpd/vhosts/aflac.fr/domains/dev.aflac.fr/httpdocs/core/lib/pdf.lib.php on line 2289 --- .../expensereport/class/expensereport.class.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/htdocs/expensereport/class/expensereport.class.php b/htdocs/expensereport/class/expensereport.class.php index 74d668f9d59..5526b5564c4 100644 --- a/htdocs/expensereport/class/expensereport.class.php +++ b/htdocs/expensereport/class/expensereport.class.php @@ -2754,6 +2754,21 @@ class ExpenseReportLine extends CommonObjectLine public $total_localtax1; public $total_localtax2; + // Multicurrency + /** + * @var int Currency ID + */ + public $fk_multicurrency; + + /** + * @var string multicurrency code + */ + public $multicurrency_code; + public $multicurrency_tx; + public $multicurrency_total_ht; + public $multicurrency_total_tva; + public $multicurrency_total_ttc; + /** * @var int ID into llx_ecm_files table to link line to attached file */ From 4cd7f7bedaceaaea1898bb04cf141127ef9c5fc6 Mon Sep 17 00:00:00 2001 From: Philippe GRAND Date: Tue, 7 Feb 2023 15:56:28 +0100 Subject: [PATCH 12/35] fix : Warning: Undefined property: pdf_standard:: in /home/httpd/vhosts/aflac.fr/domains/dev.aflac.fr/httpdocs/core/modules/expensereport/doc/pdf_standard.modules.php on line 582 --- htdocs/core/modules/expensereport/doc/pdf_standard.modules.php | 1 + 1 file changed, 1 insertion(+) diff --git a/htdocs/core/modules/expensereport/doc/pdf_standard.modules.php b/htdocs/core/modules/expensereport/doc/pdf_standard.modules.php index f365173ddbe..80418a86f91 100644 --- a/htdocs/core/modules/expensereport/doc/pdf_standard.modules.php +++ b/htdocs/core/modules/expensereport/doc/pdf_standard.modules.php @@ -92,6 +92,7 @@ class pdf_standard extends ModeleExpenseReport public $posxtva; public $posxup; public $posxqty; + public $posxtype; public $postotalht; public $postotalttc; From 9ad29faa32520163fe8e461c093891201f45146e Mon Sep 17 00:00:00 2001 From: Philippe GRAND Date: Tue, 7 Feb 2023 16:01:09 +0100 Subject: [PATCH 13/35] fix : Warning: Undefined property: pdf_standard:: in /home/httpd/vhosts/aflac.fr/domains/dev.aflac.fr/httpdocs/core/modules/expensereport/doc/pdf_standard.modules.php on line 589 --- htdocs/core/modules/expensereport/doc/pdf_standard.modules.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/htdocs/core/modules/expensereport/doc/pdf_standard.modules.php b/htdocs/core/modules/expensereport/doc/pdf_standard.modules.php index 80418a86f91..4363a7e90f1 100644 --- a/htdocs/core/modules/expensereport/doc/pdf_standard.modules.php +++ b/htdocs/core/modules/expensereport/doc/pdf_standard.modules.php @@ -1,7 +1,7 @@ * Copyright (C) 2015 Alexandre Spangaro - * Copyright (C) 2016-2022 Philippe Grand + * Copyright (C) 2016-2023 Philippe Grand * Copyright (C) 2018-2020 Frédéric France * Copyright (C) 2018 Francis Appels * Copyright (C) 2019 Markus Welters @@ -93,6 +93,7 @@ class pdf_standard extends ModeleExpenseReport public $posxup; public $posxqty; public $posxtype; + public $posxprojet; public $postotalht; public $postotalttc; From 55d6e2ead026cf7dde9124e26432d97e3b63a9b5 Mon Sep 17 00:00:00 2001 From: tnegre Date: Tue, 7 Feb 2023 17:24:16 +0100 Subject: [PATCH 14/35] Fix prelevement line : the link to the facture was to a customer facture, even when it was a facturefourn. --- htdocs/compta/prelevement/line.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/htdocs/compta/prelevement/line.php b/htdocs/compta/prelevement/line.php index cba7777b734..6eb10ce6a1a 100644 --- a/htdocs/compta/prelevement/line.php +++ b/htdocs/compta/prelevement/line.php @@ -309,7 +309,11 @@ if ($id) { print img_object($langs->trans("ShowBill"), "bill"); print ' '; - print ''.$obj->ref."\n"; + if ($type == 'bank-transfer') { + print ''.$obj->ref."\n"; + } else { + print ''.$obj->ref."\n"; + } print ''; print img_object($langs->trans("ShowCompany"), "company").' '.$obj->name."\n"; From e71ad2e23bd937ee3d448c365a997f3357dfd63a Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 7 Feb 2023 20:12:53 +0100 Subject: [PATCH 15/35] Maxi debug of OAuth module --- htdocs/admin/oauth.php | 21 +- htdocs/admin/oauthlogintokens.php | 31 ++- htdocs/core/lib/functions.lib.php | 4 +- htdocs/core/lib/oauth.lib.php | 12 +- .../modules/oauth/generic_oauthcallback.php | 49 ++-- .../modules/oauth/github_oauthcallback.php | 23 +- .../modules/oauth/google_oauthcallback.php | 5 +- .../modules/oauth/microsoft_oauthcallback.php | 211 ++++++++++++++++++ .../oauth/stripelive_oauthcallback.php | 27 +-- .../oauth/stripetest_oauthcallback.php | 27 +-- .../OAuth/Common/Storage/DoliStorage.php | 6 + .../OAuth/OAuth2/Service/AbstractService.php | 4 +- .../OAuth/OAuth2/Service/Microsoft.php | 64 +++--- htdocs/langs/en_US/admin.lang | 3 +- htdocs/langs/en_US/mails.lang | 1 - htdocs/langs/en_US/oauth.lang | 1 + 16 files changed, 348 insertions(+), 141 deletions(-) create mode 100644 htdocs/core/modules/oauth/microsoft_oauthcallback.php diff --git a/htdocs/admin/oauth.php b/htdocs/admin/oauth.php index 217dfd63cc9..2425f12d9bd 100644 --- a/htdocs/admin/oauth.php +++ b/htdocs/admin/oauth.php @@ -84,6 +84,11 @@ if ($action == 'update') { $error++; } } + if (GETPOSTISSET($constvalue.'_TENANT')) { + if (!dolibarr_set_const($db, $constvalue.'_TENANT', GETPOST($constvalue.'_TENANT'), 'chaine', 0, '', $conf->entity)) { + $error++; + } + } if (GETPOSTISSET($constvalue.'_SCOPE')) { if (is_array(GETPOST($constvalue.'_SCOPE'))) { $scopestring = implode(',', GETPOST($constvalue.'_SCOPE')); @@ -128,6 +133,8 @@ if ($action == 'confirm_delete') { $callbacktodel .= '/core/modules/oauth/stripelive_oauthcallback.php?action=delete&keyforprovider='.$provider.'&token='.newToken().'&backtourl='.urlencode($backtourl); } elseif ($label == 'OAUTH_STRIPE_TEST') { $callbacktodel .= '/core/modules/oauth/stripetest_oauthcallback.php?action=delete&keyforprovider='.$provider.'&token='.newToken().'&backtourl='.urlencode($backtourl); + } elseif ($label == 'OAUTH_MICROSOFT') { + $callbacktodel .= '/core/modules/oauth/microsoft_oauthcallback.php?action=delete&keyforprovider='.$provider.'&token='.newToken().'&backtourl='.urlencode($backtourl); } elseif ($label == 'OAUTH_OTHER') { $callbacktodel .= '/core/modules/oauth/generic_oauthcallback.php?action=delete&keyforprovider='.$provider.'&token='.newToken().'&backtourl='.urlencode($backtourl); } @@ -242,8 +249,10 @@ if (count($listinsetup) > 0) { $keyforsupportedoauth2array = preg_replace('/^OAUTH_/', '', $keyforsupportedoauth2array); $keyforsupportedoauth2array = preg_replace('/_NAME$/', '', $keyforsupportedoauth2array); if (preg_match('/^.*-/', $keyforsupportedoauth2array)) { + $keybeforeprovider = preg_replace('/-.*$/', '', $keyforsupportedoauth2array); $keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array); } else { + $keybeforeprovider = $keyforsupportedoauth2array; $keyforprovider = ''; } $keyforsupportedoauth2array = preg_replace('/-.*$/', '', $keyforsupportedoauth2array); @@ -337,6 +346,16 @@ if (count($listinsetup) > 0) { print ''; print ''; + // Tenant + if ($keybeforeprovider == 'MICROSOFT') { + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + } + // TODO Move this into token generation ? if ($supported) { if ($keyforsupportedoauth2array == 'OAUTH_OTHER_NAME') { @@ -386,7 +405,7 @@ if (count($listinsetup) > 0) { print ''; - print $form->buttonsSaveCancel("Modify", ''); + print $form->buttonsSaveCancel("Save", ''); print ''; } diff --git a/htdocs/admin/oauthlogintokens.php b/htdocs/admin/oauthlogintokens.php index 9a0532880cd..d6fcc9e0f7c 100644 --- a/htdocs/admin/oauthlogintokens.php +++ b/htdocs/admin/oauthlogintokens.php @@ -162,8 +162,10 @@ if ($mode == 'setup' && $user->admin) { $keyforsupportedoauth2array = preg_replace('/^OAUTH_/', '', $keyforsupportedoauth2array); $keyforsupportedoauth2array = preg_replace('/_NAME$/', '', $keyforsupportedoauth2array); if (preg_match('/^.*-/', $keyforsupportedoauth2array)) { + $keybeforeprovider = preg_replace('/-.*$/', '', $keyforsupportedoauth2array); $keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array); } else { + $keybeforeprovider = $keyforsupportedoauth2array; $keyforprovider = ''; } $keyforsupportedoauth2array = preg_replace('/-.*$/', '', $keyforsupportedoauth2array); @@ -179,13 +181,12 @@ if ($mode == 'setup' && $user->admin) { $state = $shortscope; // TODO USe a better state // Define $urltorenew, $urltodelete, $urltocheckperms - // TODO Use array $supportedoauth2array if ($keyforsupportedoauth2array == 'OAUTH_GITHUB_NAME') { // List of keys that will be converted into scopes (from constants 'SCOPE_state_in_uppercase' in file of service). // We pass this param list in to 'state' because we need it before and after the redirect. // Note: github does not accept csrf key inside the state parameter (only known values) - $urltorenew = $urlwithroot.'/core/modules/oauth/github_oauthcallback.php?shortscope='.urlencode($shortscope).'&state='.$shortscope.'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); + $urltorenew = $urlwithroot.'/core/modules/oauth/github_oauthcallback.php?shortscope='.urlencode($shortscope).'&state='.urlencode($shortscope).'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltodelete = $urlwithroot.'/core/modules/oauth/github_oauthcallback.php?action=delete&token='.newToken().'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltocheckperms = 'https://github.com/settings/applications/'; } elseif ($keyforsupportedoauth2array == 'OAUTH_GOOGLE_NAME') { @@ -195,17 +196,9 @@ if ($mode == 'setup' && $user->admin) { $urltorenew = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?shortscope='.urlencode($shortscope).'&state='.urlencode($state).'-'.$oauthstateanticsrf.'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltodelete = $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?action=delete&token='.newToken().'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltocheckperms = 'https://security.google.com/settings/security/permissions'; - } elseif ($keyforsupportedoauth2array == 'OAUTH_STRIPE_TEST_NAME') { - $urltorenew = $urlwithroot.'/core/modules/oauth/stripetest_oauthcallback.php?shortscope='.urlencode($shortscope).'&state='.urlencode($state).'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); - $urltodelete = ''; - $urltocheckperms = ''; - } elseif ($keyforsupportedoauth2array == 'OAUTH_STRIPE_LIVE_NAME') { - $urltorenew = $urlwithroot.'/core/modules/oauth/stripelive_oauthcallback.php?shortscope='.urlencode($shortscope).'&state='.urlencode($state).'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); - $urltodelete = ''; - $urltocheckperms = ''; - } elseif ($keyforsupportedoauth2array = 'OAUTH_OTHER_NAME') { - $urltorenew = $urlwithroot.'/core/modules/oauth/generic_oauthcallback.php?shortscope='.urlencode($shortscope).'&state='.urlencode($state).'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); - $urltodelete = ''; + } elseif (!empty($supportedoauth2array[$keyforsupportedoauth2array]['returnurl'])) { + $urltorenew = $urlwithroot.$supportedoauth2array[$keyforsupportedoauth2array]['returnurl'].'?shortscope='.urlencode($shortscope).'&state='.urlencode($state).'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); + $urltodelete = $urlwithroot.$supportedoauth2array[$keyforsupportedoauth2array]['returnurl'].'?action=delete&token='.newToken().'&backtourl='.urlencode(DOL_URL_ROOT.'/admin/oauthlogintokens.php'); $urltocheckperms = ''; } else { $urltorenew = ''; @@ -220,17 +213,19 @@ if ($mode == 'setup' && $user->admin) { $urltodelete .= '&keyforprovider='.urlencode($keyforprovider); } - // Show value of token $tokenobj = null; // Token require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php'; // Dolibarr storage - $storage = new DoliStorage($db, $conf); + $storage = new DoliStorage($db, $conf, $keyforprovider); try { + // $OAUTH_SERVICENAME is for example 'Google-keyforprovider' + print $OAUTH_SERVICENAME; $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME); } catch (Exception $e) { // Return an error if token not found + //print $e->getMessage(); } // Set other properties @@ -321,7 +316,11 @@ if ($mode == 'setup' && $user->admin) { // Links to delete/checks token if (is_object($tokenobj)) { //test on $storage->hasAccessToken($OAUTH_SERVICENAME) ? - print ''.$langs->trans('DeleteAccess').'
'; + if ($urltodelete) { + print ''.$langs->trans('DeleteAccess').'
'; + } else { + print ''.$langs->trans('GoOnTokenProviderToDeleteToken').'
'; + } } // Request remote token if ($urltorenew) { diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index d47b71d39cf..e94b9d5e4c0 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -4102,7 +4102,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', 'proposal', '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', 'user-tie', 'vcard', 'wrench', - 'github', 'google', 'jabber', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'youtube', 'google-plus-g', 'whatsapp', + 'github', 'google', 'jabber', 'microsoft', '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', @@ -4123,7 +4123,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', 'google', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'stripe', 'stripe-s', 'youtube', 'google-plus-g', 'whatsapp'))) { + if (in_array($pictowithouttext, array('black-tie', 'github', 'google', 'microsoft', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'stripe', 'stripe-s', 'youtube', 'google-plus-g', 'whatsapp'))) { $fa = 'fab'; } diff --git a/htdocs/core/lib/oauth.lib.php b/htdocs/core/lib/oauth.lib.php index 83359ef1c65..4f504196b47 100644 --- a/htdocs/core/lib/oauth.lib.php +++ b/htdocs/core/lib/oauth.lib.php @@ -25,15 +25,17 @@ // Supported OAUTH (a provider is supported when a file xxx_oauthcallback.php is available into htdocs/core/modules/oauth) $supportedoauth2array = array( - 'OAUTH_GOOGLE_NAME'=>array('callbackfile' => 'google', 'picto' => 'google', 'urlforapp' => 'OAUTH_GOOGLE_DESC', 'name'=>'Google', 'urlforcredentials'=>'https://console.developers.google.com/', 'availablescopes'=> 'userinfo_email,userinfo_profile,openid,email,profile,cloud_print,admin_directory_user,gmail_full'), + 'OAUTH_GOOGLE_NAME'=>array('callbackfile' => 'google', 'picto' => 'google', 'urlforapp' => 'OAUTH_GOOGLE_DESC', 'name'=>'Google', 'urlforcredentials'=>'https://console.developers.google.com/', 'availablescopes'=> 'userinfo_email,userinfo_profile,openid,email,profile,cloud_print,admin_directory_user,gmail_full', 'returnurl'=>'/core/modules/oauth/google_oauthcallback.php'), ); if (isModEnabled('stripe')) { - $supportedoauth2array['OAUTH_STRIPE_TEST_NAME'] = array('callbackfile' => 'stripetest', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeTest', 'urlforcredentials'=>'', 'availablescopes'=>'read_write'); - $supportedoauth2array['OAUTH_STRIPE_LIVE_NAME'] = array('callbackfile' => 'stripelive', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeLive', 'urlforcredentials'=>'', 'availablescopes'=>'read_write'); + $supportedoauth2array['OAUTH_STRIPE_TEST_NAME'] = array('callbackfile' => 'stripetest', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeTest', 'urlforcredentials'=>'', 'availablescopes'=>'read_write', 'returnurl'=>'/core/modules/oauth/stripetest_oauthcallback.php'); + $supportedoauth2array['OAUTH_STRIPE_LIVE_NAME'] = array('callbackfile' => 'stripelive', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeLive', 'urlforcredentials'=>'', 'availablescopes'=>'read_write', 'returnurl'=>'/core/modules/oauth/stripelive_oauthcallback.php'); } -$supportedoauth2array['OAUTH_GITHUB_NAME'] = array('callbackfile' => 'github', 'picto' => 'github', 'urlforapp' => 'OAUTH_GITHUB_DESC', 'name'=>'GitHub', 'urlforcredentials'=>'https://github.com/settings/developers', 'availablescopes'=>'user,public_repo'); +$supportedoauth2array['OAUTH_GITHUB_NAME'] = array('callbackfile' => 'github', 'picto' => 'github', 'urlforapp' => 'OAUTH_GITHUB_DESC', 'name'=>'GitHub', 'urlforcredentials'=>'https://github.com/settings/developers', 'availablescopes'=>'user,public_repo', 'returnurl'=>'/core/modules/oauth/github_oauthcallback.php'); if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 2) { - $supportedoauth2array['OAUTH_OTHER_NAME'] = array('callbackfile' => 'generic', 'picto' => 'generic', 'urlforapp' => 'OAUTH_OTHER_DESC', 'name'=>'Other', 'urlforcredentials'=>'', 'availablescopes'=>'Standard'); + $supportedoauth2array['OAUTH_OTHER_NAME'] = array('callbackfile' => 'generic', 'picto' => 'generic', 'urlforapp' => 'OAUTH_OTHER_DESC', 'name'=>'Other', 'urlforcredentials'=>'', 'availablescopes'=>'Standard', 'returnurl'=>'/core/modules/oauth/generic_oauthcallback.php'); + // See https://learn.microsoft.com/fr-fr/azure/active-directory/develop/quickstart-register-app#register-an-application + $supportedoauth2array['OAUTH_MICROSOFT_NAME'] = array('callbackfile' => 'microsoft', 'picto' => 'microsoft', 'urlforapp' => 'OAUTH_MICROSOFT_DESC', 'name'=>'Microsoft', 'urlforcredentials'=>'https://portal.azure.com/', 'availablescopes'=>'openid,offline_access,profile,email,IMAP.AccessAsUser.All', 'returnurl'=>'/core/modules/oauth/microsoft_oauthcallback.php'); } diff --git a/htdocs/core/modules/oauth/generic_oauthcallback.php b/htdocs/core/modules/oauth/generic_oauthcallback.php index 34422111d5d..a394c7f4986 100644 --- a/htdocs/core/modules/oauth/generic_oauthcallback.php +++ b/htdocs/core/modules/oauth/generic_oauthcallback.php @@ -66,7 +66,7 @@ $httpClient = new \OAuth\Common\Http\Client\CurlClient(); $serviceFactory->setHttpClient($httpClient); // Dolibarr storage -$storage = new DoliStorage($db, $conf); +$storage = new DoliStorage($db, $conf, $keyforprovider); // Setup the credentials for the requests $keyforparamid = 'OAUTH_'.$genericstring.($keyforprovider ? '-'.$keyforprovider : '').'_ID'; @@ -77,9 +77,11 @@ $credentials = new Credentials( $currentUri->getAbsoluteUri() ); +$state = GETPOST('state'); + $requestedpermissionsarray = array(); -if (GETPOST('state')) { - $requestedpermissionsarray = explode(',', GETPOST('state')); // Example: 'user'. 'state' parameter is standard to retrieve some parameters back +if ($state) { + $requestedpermissionsarray = explode(',', $state); // Example: 'user'. 'state' parameter is standard to retrieve some parameters back } if ($action != 'delete' && empty($requestedpermissionsarray)) { print 'Error, parameter state is not defined'; @@ -88,7 +90,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 -$apiService = $serviceFactory->createService($genericstring, $credentials, $storage, $requestedpermissionsarray); +// ucfirst(strtolower($genericstring)) must be the name of a class into OAuth/OAuth2/Services/Xxxx +$apiService = $serviceFactory->createService(ucfirst(strtolower($genericstring)), $credentials, $storage, $requestedpermissionsarray); /* var_dump($genericstring.($keyforprovider ? '-'.$keyforprovider : '')); @@ -128,35 +131,25 @@ if ($action == 'delete') { exit(); } -if (GETPOST('code')) { // We are coming from oauth provider page +if (GETPOST('code') || GETPOST('error')) { // We are coming from oauth provider page // We should have //$_GET=array('code' => string 'aaaaaaaaaaaaaa' (length=20), 'state' => string 'user,public_repo' (length=16)) - dol_syslog("We are coming from the oauth provider page"); - //llxHeader('',$langs->trans("OAuthSetup")); - - //$linkback=''.$langs->trans("BackToModuleList").''; - //print load_fiche_titre($langs->trans("OAuthSetup"),$linkback,'title_setup'); - - //print dol_get_fiche_head(); - // retrieve the CSRF state parameter - $state = GETPOSTISSET('state') ? GETPOST('state') : null; - //print ''; + dol_syslog("We are coming from the oauth provider page code=".dol_trunc(GETPOST('code'), 5)." error=".GETPOST('error')); // This was a callback request from service, get the token try { - //var_dump($_GET['code']); //var_dump($state); - //var_dump($apiService); // OAuth\OAuth2\Service\GitHub + //var_dump($apiService); // OAuth\OAuth2\Service\Xxx - //$token = $apiService->requestAccessToken(GETPOST('code'), $state); - $token = $apiService->requestAccessToken(GETPOST('code')); - // Github is a service that does not need state to be stored. - // Into constructor of GitHub, the call - // parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri) - // has not the ending parameter to true like the Google class constructor. + if (GETPOST('error')) { + setEventMessages(GETPOST('error').' '.GETPOST('error_description'), null, 'errors'); + } else { + //$token = $apiService->requestAccessToken(GETPOST('code'), $state); + $token = $apiService->requestAccessToken(GETPOST('code')); - setEventMessages($langs->trans('NewTokenStored'), null, 'mesgs'); // Stored into object managed by class DoliStorage so into table oauth_token + setEventMessages($langs->trans('NewTokenStored'), null, 'mesgs'); // Stored into object managed by class DoliStorage so into table oauth_token + } $backtourl = $_SESSION["backtourlsavedbeforeoauthjump"]; unset($_SESSION["backtourlsavedbeforeoauthjump"]); @@ -166,15 +159,17 @@ if (GETPOST('code')) { // We are coming from oauth provider page } 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; $_SESSION["oauthkeyforproviderbeforeoauthjump"] = $keyforprovider; $_SESSION['oauthstateanticsrf'] = $state; // This may create record into oauth_state before the header redirect. // Creation of record with state in this tables depend on the Provider used (see its constructor). - if (GETPOST('state')) { - $url = $apiService->getAuthorizationUri(array('state' => GETPOST('state'))); + if ($state) { + $url = $apiService->getAuthorizationUri(array('state' => $state)); } else { $url = $apiService->getAuthorizationUri(); // Parameter state will be randomly generated } diff --git a/htdocs/core/modules/oauth/github_oauthcallback.php b/htdocs/core/modules/oauth/github_oauthcallback.php index 24140718880..7656a1cda37 100644 --- a/htdocs/core/modules/oauth/github_oauthcallback.php +++ b/htdocs/core/modules/oauth/github_oauthcallback.php @@ -65,7 +65,7 @@ $httpClient = new \OAuth\Common\Http\Client\CurlClient(); $serviceFactory->setHttpClient($httpClient); // Dolibarr storage -$storage = new DoliStorage($db, $conf); +$storage = new DoliStorage($db, $conf, $keyforprovider); // Setup the credentials for the requests $keyforparamid = 'OAUTH_GITHUB'.($keyforprovider ? '-'.$keyforprovider : '').'_ID'; @@ -115,30 +115,21 @@ 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 // We should have //$_GET=array('code' => string 'aaaaaaaaaaaaaa' (length=20), 'state' => string 'user,public_repo' (length=16)) - dol_syslog("We are coming from the oauth provider page"); - //llxHeader('',$langs->trans("OAuthSetup")); - - //$linkback=''.$langs->trans("BackToModuleList").''; - //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 '
'; + dol_syslog("We are coming from the oauth provider page code=".dol_trunc(GETPOST('code'), 5)); // This was a callback request from service, get the token try { - //var_dump($_GET['code']); //var_dump($state); //var_dump($apiService); // OAuth\OAuth2\Service\GitHub - //$token = $apiService->requestAccessToken($_GET['code'], $state); - $token = $apiService->requestAccessToken($_GET['code']); - // Github is a service that does not need state to be stored. + //$token = $apiService->requestAccessToken(GETPOST('code'), $state); + $token = $apiService->requestAccessToken(GETPOST('code')); + // Github is a service that does not need state to be stored as second paramater of requestAccessToken + // Into constructor of GitHub, the call // parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri) // has not the ending parameter to true like the Google class constructor. diff --git a/htdocs/core/modules/oauth/google_oauthcallback.php b/htdocs/core/modules/oauth/google_oauthcallback.php index ed0caa1a4ff..c26187e4475 100644 --- a/htdocs/core/modules/oauth/google_oauthcallback.php +++ b/htdocs/core/modules/oauth/google_oauthcallback.php @@ -137,7 +137,7 @@ if ($action == 'delete') { } if (GETPOST('code')) { // We are coming from oauth provider page. - dol_syslog("We are coming from the oauth provider page keyforprovider=".$keyforprovider); + dol_syslog("We are coming from the oauth provider page keyforprovider=".$keyforprovider." code=".dol_trunc(GETPOST('code'), 5)); // 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']) { @@ -146,7 +146,6 @@ if (GETPOST('code')) { // We are coming from oauth provider page. } 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 @@ -193,7 +192,7 @@ if (GETPOST('code')) { // We are coming from oauth provider page. } } } else { - // If we enter this page without 'code' parameter, we arrive here. this is the case when we want to get the redirect + // 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; $_SESSION["oauthkeyforproviderbeforeoauthjump"] = $keyforprovider; diff --git a/htdocs/core/modules/oauth/microsoft_oauthcallback.php b/htdocs/core/modules/oauth/microsoft_oauthcallback.php new file mode 100644 index 00000000000..ed47eec06e1 --- /dev/null +++ b/htdocs/core/modules/oauth/microsoft_oauthcallback.php @@ -0,0 +1,211 @@ + + * Copyright (C) 2015 Frederic France + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file htdocs/core/modules/oauth/microsoft_oauthcallback.php + * \ingroup oauth + * \brief Page to get oauth callback + */ + +// Load Dolibarr environment +require '../../../main.inc.php'; +require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php'; +use OAuth\Common\Storage\DoliStorage; +use OAuth\Common\Consumer\Credentials; +use OAuth\OAuth2\Service\GitHub; + +// Define $urlwithroot +$urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root)); +$urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file +//$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current + + +$action = GETPOST('action', 'aZ09'); +$backtourl = GETPOST('backtourl', 'alpha'); +$keyforprovider = GETPOST('keyforprovider', 'aZ09'); +if (empty($keyforprovider) && !empty($_SESSION["oauthkeyforproviderbeforeoauthjump"]) && (GETPOST('code') || $action == 'delete')) { + $keyforprovider = $_SESSION["oauthkeyforproviderbeforeoauthjump"]; +} +$genericstring = 'MICROSOFT'; + + +/** + * Create a new instance of the URI class with the current URI, stripping the query string + */ +$uriFactory = new \OAuth\Common\Http\Uri\UriFactory(); +//$currentUri = $uriFactory->createFromSuperGlobalArray($_SERVER); +//$currentUri->setQuery(''); +$currentUri = $uriFactory->createFromAbsolute($urlwithroot.'/core/modules/oauth/microsoft_oauthcallback.php'); + + +/** + * Load the credential for the service + */ + +/** @var $serviceFactory \OAuth\ServiceFactory An OAuth service factory. */ +$serviceFactory = new \OAuth\ServiceFactory(); +$httpClient = new \OAuth\Common\Http\Client\CurlClient(); +// TODO Set options for proxy and timeout +// $params=array('CURLXXX'=>value, ...) +//$httpClient->setCurlParameters($params); +$serviceFactory->setHttpClient($httpClient); + +// Dolibarr storage +$storage = new DoliStorage($db, $conf, $keyforprovider); + +// Setup the credentials for the requests +$keyforparamid = 'OAUTH_'.$genericstring.($keyforprovider ? '-'.$keyforprovider : '').'_ID'; +$keyforparamsecret = 'OAUTH_'.$genericstring.($keyforprovider ? '-'.$keyforprovider : '').'_SECRET'; +$keyforparamtenant = 'OAUTH_'.$genericstring.($keyforprovider ? '-'.$keyforprovider : '').'_TENANT'; +$credentials = new Credentials( + getDolGlobalString($keyforparamid), + getDolGlobalString($keyforparamsecret), + $currentUri->getAbsoluteUri() +); + +$state = GETPOST('state'); + +$requestedpermissionsarray = array(); +if ($state) { + $requestedpermissionsarray = explode(',', $state); // Example: 'user'. 'state' parameter is standard to retrieve some parameters back +} +if ($action != 'delete' && empty($requestedpermissionsarray)) { + print 'Error, parameter state is not defined'; + exit; +} +//var_dump($requestedpermissionsarray);exit; + +// Instantiate the Api service using the credentials, http client and storage mechanism for the token +// ucfirst(strtolower($genericstring)) must be the name of a class into OAuth/OAuth2/Services/Xxxx +// $requestedpermissionsarray contains list of scopes. +// Conversion into URL is done by Reflection on constant with name SCOPE_scope_in_uppercase +try { + $apiService = $serviceFactory->createService(ucfirst(strtolower($genericstring)), $credentials, $storage, $requestedpermissionsarray); +} catch (Exception $e) { + print $e->getMessage(); + exit; +} +/* +var_dump($genericstring.($keyforprovider ? '-'.$keyforprovider : '')); +var_dump($credentials); +var_dump($storage); +var_dump($requestedpermissionsarray); +*/ + +if (empty($apiService)) { + print 'Error, failed to create serviceFactory'; + exit; +} + +// access type needed to have oauth provider refreshing token +//$apiService->setAccessType('offline'); + +$langs->load("oauth"); + +if (!getDolGlobalString($keyforparamid)) { + accessforbidden('Setup of service is not complete. Customer ID is missing'); +} +if (!getDolGlobalString($keyforparamsecret)) { + accessforbidden('Setup of service is not complete. Secret key is missing'); +} + + +/* + * Actions + */ + +if ($action == 'delete') { + $storage->clearToken($genericstring); + + setEventMessages($langs->trans('TokenDeleted'), null, 'mesgs'); + + header('Location: '.$backtourl); + exit(); +} + +//dol_syslog("GET=".join(',', $_GET)); + + +if (GETPOST('code') || GETPOST('error')) { // We are coming from oauth provider page + // We should have + //$_GET=array('code' => string 'aaaaaaaaaaaaaa' (length=20), 'state' => string 'user,public_repo' (length=16)) + + dol_syslog("We are coming from the oauth provider page code=".dol_trunc(GETPOST('code'), 5)." error=".GETPOST('error')); + + // This was a callback request from service, get the token + try { + //var_dump($state); + //var_dump($apiService); // OAuth\OAuth2\Service\Microsoft + + if (GETPOST('error')) { + setEventMessages(GETPOST('error').' '.GETPOST('error_description'), null, 'errors'); + } else { + $apiService->tenant = getDolGlobalString($keyforparamtenant); + + //$token = $apiService->requestAccessToken(GETPOST('code'), $state); + $token = $apiService->requestAccessToken(GETPOST('code')); + // Microsoft is a service that does not need state to be stored as second paramater of requestAccessToken + + setEventMessages($langs->trans('NewTokenStored'), null, 'mesgs'); // Stored into object managed by class DoliStorage so into table oauth_token + } + + $backtourl = $_SESSION["backtourlsavedbeforeoauthjump"]; + unset($_SESSION["backtourlsavedbeforeoauthjump"]); + + header('Location: '.$backtourl); + exit(); + } catch (Exception $e) { + print $e->getMessage(); + } +} else { + // If 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; + $_SESSION["oauthkeyforproviderbeforeoauthjump"] = $keyforprovider; + $_SESSION['oauthstateanticsrf'] = $state; + + //if (!preg_match('/^forlogin/', $state)) { + // $apiService->setApprouvalPrompt('auto'); + //} + $apiService->tenant = getDolGlobalString($keyforparamtenant); + + // 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 ($state) { + $url = $apiService->getAuthorizationUri(array('state' => $state)); + } else { + $url = $apiService->getAuthorizationUri(); // Parameter state will be randomly generated + } + + // Show url to get authorization + //var_dump((string) $url);exit; + dol_syslog("Redirect to url=".$url); + + // we go on oauth provider authorization page + header('Location: '.$url); + exit(); +} + + +/* + * View + */ + +// No view at all, just actions + +$db->close(); diff --git a/htdocs/core/modules/oauth/stripelive_oauthcallback.php b/htdocs/core/modules/oauth/stripelive_oauthcallback.php index ef35b6573cc..bc16b44461a 100644 --- a/htdocs/core/modules/oauth/stripelive_oauthcallback.php +++ b/htdocs/core/modules/oauth/stripelive_oauthcallback.php @@ -65,7 +65,7 @@ $httpClient = new \OAuth\Common\Http\Client\CurlClient(); $serviceFactory->setHttpClient($httpClient); // Dolibarr storage -$storage = new DoliStorage($db, $conf); +$storage = new DoliStorage($db, $conf, $keyforprovider); // Setup the credentials for the requests $keyforparamid = 'OAUTH_STRIPE_LIVE'.($keyforprovider ? '-'.$keyforprovider : '').'_ID'; @@ -121,33 +121,20 @@ 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 // We should have //$_GET=array('code' => string 'aaaaaaaaaaaaaa' (length=20), 'state' => string 'user,public_repo' (length=16)) - dol_syslog("We are coming from the oauth provider page"); - //llxHeader('',$langs->trans("OAuthSetup")); - - //$linkback=''.$langs->trans("BackToModuleList").''; - //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 '
'; + dol_syslog("We are coming from the oauth provider page code=".dol_trunc(GETPOST('code'), 5)); // This was a callback request from service, get the token try { - //var_dump($_GET['code']); //var_dump($state); - //var_dump($apiService); // OAuth\OAuth2\Service\GitHub + //var_dump($apiService); // OAuth\OAuth2\Service\Stripe - //$token = $apiService->requestAccessToken($_GET['code'], $state); - $token = $apiService->requestAccessToken($_GET['code']); - // Github is a service that does not need state to be stored. - // Into constructor of GitHub, the call - // parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri) - // has not the ending parameter to true like the Google class constructor. + //$token = $apiService->requestAccessToken(GETPOST('code'), $state); + $token = $apiService->requestAccessToken(GETPOST('code')); + // Stripe is a service that does not need state to be stored as second paramater of requestAccessToken setEventMessages($langs->trans('NewTokenStored'), null, 'mesgs'); // Stored into object managed by class DoliStorage so into table oauth_token diff --git a/htdocs/core/modules/oauth/stripetest_oauthcallback.php b/htdocs/core/modules/oauth/stripetest_oauthcallback.php index a5d481dbef5..12d133da14c 100644 --- a/htdocs/core/modules/oauth/stripetest_oauthcallback.php +++ b/htdocs/core/modules/oauth/stripetest_oauthcallback.php @@ -65,7 +65,7 @@ $httpClient = new \OAuth\Common\Http\Client\CurlClient(); $serviceFactory->setHttpClient($httpClient); // Dolibarr storage -$storage = new DoliStorage($db, $conf); +$storage = new DoliStorage($db, $conf, $keyforprovider); // Setup the credentials for the requests $keyforparamid = 'OAUTH_STRIPE_TEST'.($keyforprovider ? '-'.$keyforprovider : '').'_ID'; @@ -121,33 +121,20 @@ 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 // We should have //$_GET=array('code' => string 'aaaaaaaaaaaaaa' (length=20), 'state' => string 'user,public_repo' (length=16)) - dol_syslog("We are coming from the oauth provider page"); - //llxHeader('',$langs->trans("OAuthSetup")); - - //$linkback=''.$langs->trans("BackToModuleList").''; - //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 '
'; + dol_syslog("We are coming from the oauth provider page code=".dol_trunc(GETPOST('code'), 5)); // This was a callback request from service, get the token try { - //var_dump($_GET['code']); //var_dump($state); - //var_dump($apiService); // OAuth\OAuth2\Service\GitHub + //var_dump($apiService); // OAuth\OAuth2\Service\Stripe - //$token = $apiService->requestAccessToken($_GET['code'], $state); - $token = $apiService->requestAccessToken($_GET['code']); - // Github is a service that does not need state to be stored. - // Into constructor of GitHub, the call - // parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri) - // has not the ending parameter to true like the Google class constructor. + //$token = $apiService->requestAccessToken(GETPOST('code'), $state); + $token = $apiService->requestAccessToken(GETPOST('code')); + // Stripe is a service that does not need state to be stored as second paramater of requestAccessToken setEventMessages($langs->trans('NewTokenStored'), null, 'mesgs'); // Stored into object managed by class DoliStorage so into table oauth_token diff --git a/htdocs/includes/OAuth/Common/Storage/DoliStorage.php b/htdocs/includes/OAuth/Common/Storage/DoliStorage.php index 3e09e53fbe6..cf280262e99 100644 --- a/htdocs/includes/OAuth/Common/Storage/DoliStorage.php +++ b/htdocs/includes/OAuth/Common/Storage/DoliStorage.php @@ -126,6 +126,9 @@ class DoliStorage implements TokenStorageInterface $sql.= " SET token = '".$this->db->escape($serializedToken)."'"; $sql.= " WHERE rowid = ".((int) $obj['rowid']); $resql = $this->db->query($sql); + if (!$resql) { + dol_print_error($this->db); + } } else { // save $sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token (service, token, entity, datec)"; @@ -133,6 +136,9 @@ class DoliStorage implements TokenStorageInterface $sql .= " '".$this->db->idate(dol_now())."'"; $sql .= ")"; $resql = $this->db->query($sql); + if (!$resql) { + dol_print_error($this->db); + } } //print $sql; diff --git a/htdocs/includes/OAuth/OAuth2/Service/AbstractService.php b/htdocs/includes/OAuth/OAuth2/Service/AbstractService.php index 996506afbec..0de0219306a 100644 --- a/htdocs/includes/OAuth/OAuth2/Service/AbstractService.php +++ b/htdocs/includes/OAuth/OAuth2/Service/AbstractService.php @@ -56,8 +56,8 @@ abstract class AbstractService extends BaseAbstractService implements ServiceInt $this->stateParameterInAuthUrl = $stateParameterInAutUrl; foreach ($scopes as $scope) { - if (!$this->isValidScope($scope)) { - throw new InvalidScopeException('Scope ' . $scope . ' is not valid for service ' . get_class($this)); + if (!$this->isValidScope($scope)) { + throw new InvalidScopeException('Scope ' . $scope . ' is not valid for service ' . get_class($this)); } } diff --git a/htdocs/includes/OAuth/OAuth2/Service/Microsoft.php b/htdocs/includes/OAuth/OAuth2/Service/Microsoft.php index c815b22bd44..b1b6a042c01 100644 --- a/htdocs/includes/OAuth/OAuth2/Service/Microsoft.php +++ b/htdocs/includes/OAuth/OAuth2/Service/Microsoft.php @@ -12,30 +12,35 @@ use OAuth\Common\Http\Uri\UriInterface; class Microsoft extends AbstractService { - const SCOPE_BASIC = 'wl.basic'; - const SCOPE_OFFLINE = 'wl.offline_access'; - const SCOPE_SIGNIN = 'wl.signin'; - const SCOPE_BIRTHDAY = 'wl.birthday'; - const SCOPE_CALENDARS = 'wl.calendars'; - const SCOPE_CALENDARS_UPDATE = 'wl.calendars_update'; - const SCOPE_CONTACTS_BIRTHDAY = 'wl.contacts_birthday'; - const SCOPE_CONTACTS_CREATE = 'wl.contacts_create'; - const SCOPE_CONTACTS_CALENDARS = 'wl.contacts_calendars'; - const SCOPE_CONTACTS_PHOTOS = 'wl.contacts_photos'; - const SCOPE_CONTACTS_SKYDRIVE = 'wl.contacts_skydrive'; - const SCOPE_EMAILS = 'wl.emails'; - const SCOPE_EVENTS_CREATE = 'wl.events_create'; - const SCOPE_MESSENGER = 'wl.messenger'; - const SCOPE_PHONE_NUMBERS = 'wl.phone_numbers'; - const SCOPE_PHOTOS = 'wl.photos'; - const SCOPE_POSTAL_ADDRESSES = 'wl.postal_addresses'; - const SCOPE_SHARE = 'wl.share'; - const SCOPE_SKYDRIVE = 'wl.skydrive'; - const SCOPE_SKYDRIVE_UPDATE = 'wl.skydrive_update'; - const SCOPE_WORK_PROFILE = 'wl.work_profile'; - const SCOPE_APPLICATIONS = 'wl.applications'; - const SCOPE_APPLICATIONS_CREATE = 'wl.applications_create'; - const SCOPE_IMAP = 'wl.imap'; + const SCOPE_BASIC = 'basic'; + const SCOPE_OFFLINE_ACCESS = 'offline_access'; + const SCOPE_SIGNIN = 'signin'; + const SCOPE_BIRTHDAY = 'birthday'; + const SCOPE_CALENDARS = 'calendars'; + const SCOPE_CALENDARS_UPDATE = 'calendars_update'; + const SCOPE_CONTACTS_BIRTHDAY = 'contacts_birthday'; + const SCOPE_CONTACTS_CREATE = 'contacts_create'; + const SCOPE_CONTACTS_CALENDARS = 'contacts_calendars'; + const SCOPE_CONTACTS_PHOTOS = 'contacts_photos'; + const SCOPE_CONTACTS_SKYDRIVE = 'contacts_skydrive'; + const SCOPE_EMAIL = 'email'; + const SCOPE_EVENTS_CREATE = 'events_create'; + const SCOPE_MESSENGER = 'messenger'; + const SCOPE_OPENID = 'openid'; + const SCOPE_PHONE_NUMBERS = 'phone_numbers'; + const SCOPE_PHOTOS = 'photos'; + const SCOPE_POSTAL_ADDRESSES = 'postal_addresses'; + const SCOPE_PROFILE = 'profile'; + const SCOPE_SHARE = 'share'; + const SCOPE_SKYDRIVE = 'skydrive'; + const SCOPE_SKYDRIVE_UPDATE = 'skydrive_update'; + const SCOPE_WORK_PROFILE = 'work_profile'; + const SCOPE_APPLICATIONS = 'applications'; + const SCOPE_APPLICATIONS_CREATE = 'applications_create'; + const SCOPE_IMAP = 'imap'; + const SOCPE_IMAP_AccessAsUser_All='IMAP.AccessAsUser.All'; + + public string $tenant; /** * MS uses some magical not officialy supported scope to get even moar info like full emailaddresses. @@ -48,7 +53,8 @@ class Microsoft extends AbstractService * * Considering this scope is not officially supported: use with care */ - const SCOPE_CONTACTS_EMAILS = 'wl.contacts_emails'; + const SCOPE_CONTACTS_EMAILS = 'contacts_emails'; + public function __construct( CredentialsInterface $credentials, @@ -69,7 +75,9 @@ class Microsoft extends AbstractService */ public function getAuthorizationEndpoint() { - return new Uri('https://login.live.com/oauth20_authorize.srf'); + //return new Uri('https://login.live.com/oauth20_authorize.srf'); + //return new Uri('https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize'); + return new Uri('https://login.microsoftonline.com/'.$this->tenant.'/oauth2/v2.0/authorize'); } /** @@ -77,7 +85,9 @@ class Microsoft extends AbstractService */ public function getAccessTokenEndpoint() { - return new Uri('https://login.live.com/oauth20_token.srf'); + //return new Uri('https://login.live.com/oauth20_token.srf'); + //return new Uri('https://login.microsoftonline.com/organizations/oauth2/v2.0/token'); + return new Uri('https://login.microsoftonline.com/'.$this->tenant.'/oauth2/v2.0/token'); } /** diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index 66210ef9d52..29551ac0874 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -2348,4 +2348,5 @@ AllowExternalDownload=Allow external download (without login, using a shared lin DeadlineDayVATSubmission=Deadline day for vat submission on the next month MaxNumberOfAttachementOnForms=Max number of joinded files in a form IfDefinedUseAValueBeetween=If defined, use a value between %s and %s -MAIN_IMAP_USE_PHPIMAP=Use the PHP-IMAP library for IMAP instead of native PHP IMAP. This also allow the use of OAuth2 connection for IMAP. \ No newline at end of file +EMailsInGoingDesc=Incoming emails are managed by the module %s. You must enable and configure it if you need to support ingoing emails. +MAIN_IMAP_USE_PHPIMAP=Use the PHP-IMAP library for IMAP instead of native PHP IMAP. This also allow the use of OAuth2 connection for IMAP (module OAuth must also be activated). diff --git a/htdocs/langs/en_US/mails.lang b/htdocs/langs/en_US/mails.lang index a112de8ceb2..f83637c9c40 100644 --- a/htdocs/langs/en_US/mails.lang +++ b/htdocs/langs/en_US/mails.lang @@ -179,4 +179,3 @@ RecordCreatedByEmailCollector=Record created by the Email Collector %s from emai DefaultBlacklistMailingStatus=Default value for field '%s' when creating a new contact DefaultStatusEmptyMandatory=Empty but mandatory WarningLimitSendByDay=WARNING: The setup or contract of your instance limits your number of emails per day to %s. Trying to send more may result in having your instance slow down or suspended. Please contact your support if you need a higher quota. -EMailsInGoingDesc=Incoming emails are managed by the module %s. You must enable and configure it if you need to support ingoing emails. diff --git a/htdocs/langs/en_US/oauth.lang b/htdocs/langs/en_US/oauth.lang index 01bb08e38bd..e773c470b30 100644 --- a/htdocs/langs/en_US/oauth.lang +++ b/htdocs/langs/en_US/oauth.lang @@ -33,6 +33,7 @@ OAUTH_STRIPE_TEST_NAME=OAuth Stripe Test OAUTH_STRIPE_LIVE_NAME=OAuth Stripe Live OAUTH_ID=OAuth ID OAUTH_SECRET=OAuth secret +OAUTH_TENANT=OAuth tenant OAuthProviderAdded=OAuth provider added AOAuthEntryForThisProviderAndLabelAlreadyHasAKey=An OAuth entry for this provider and label already exists URLOfServiceForAuthorization=URL provided by OAuth service for authentication From d82a8dc7b137a07d93561c8bf9bb270146b0033a Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 7 Feb 2023 20:35:11 +0100 Subject: [PATCH 16/35] Trans --- htdocs/langs/en_US/admin.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index 29551ac0874..46339a7bf6d 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -2349,4 +2349,4 @@ DeadlineDayVATSubmission=Deadline day for vat submission on the next month MaxNumberOfAttachementOnForms=Max number of joinded files in a form IfDefinedUseAValueBeetween=If defined, use a value between %s and %s EMailsInGoingDesc=Incoming emails are managed by the module %s. You must enable and configure it if you need to support ingoing emails. -MAIN_IMAP_USE_PHPIMAP=Use the PHP-IMAP library for IMAP instead of native PHP IMAP. This also allow the use of OAuth2 connection for IMAP (module OAuth must also be activated). +MAIN_IMAP_USE_PHPIMAP=Use the PHP-IMAP library for IMAP instead of native PHP IMAP. This also allows the use of an OAuth2 connection for IMAP (module OAuth must also be activated). From 411fbc1a045e877d3849e203bdd0cdf52cb69e7e Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 8 Feb 2023 00:13:42 +0100 Subject: [PATCH 17/35] FIX Bug in php-imap library. Fatal error if imap connet fails --- .../webklex/php-imap/src/Connection/Protocols/Protocol.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/includes/webklex/php-imap/src/Connection/Protocols/Protocol.php b/htdocs/includes/webklex/php-imap/src/Connection/Protocols/Protocol.php index ef01d46ec9b..6087ac55b75 100644 --- a/htdocs/includes/webklex/php-imap/src/Connection/Protocols/Protocol.php +++ b/htdocs/includes/webklex/php-imap/src/Connection/Protocols/Protocol.php @@ -190,7 +190,7 @@ abstract class Protocol implements ProtocolInterface { STREAM_CLIENT_CONNECT, stream_context_create($this->defaultSocketOptions($transport)) ); - stream_set_timeout($stream, $timeout); + //stream_set_timeout($stream, $timeout); // Hang id $strem empty and already done line 199 if (!$stream) { throw new ConnectionFailedException($errstr, $errno); From 310fd4e369c5c38d8d99384f2c71e4d5d3e5d6e0 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 8 Feb 2023 01:28:14 +0100 Subject: [PATCH 18/35] Debug IMAP-PHP for MS --- htdocs/admin/emailcollector_card.php | 205 +++++++++--------- htdocs/admin/oauthlogintokens.php | 6 +- htdocs/core/class/CMailFile.class.php | 4 +- htdocs/core/lib/oauth.lib.php | 2 +- htdocs/core/lib/security.lib.php | 3 +- .../modules/oauth/google_oauthcallback.php | 2 + .../modules/oauth/microsoft_oauthcallback.php | 4 +- .../modules/printing/printgcp.modules.php | 19 +- .../class/emailcollector.class.php | 2 +- .../OAuth/Common/Storage/DoliStorage.php | 63 +++++- .../OAuth/OAuth2/Service/Microsoft.php | 21 +- htdocs/printing/admin/printing.php | 4 +- 12 files changed, 204 insertions(+), 131 deletions(-) diff --git a/htdocs/admin/emailcollector_card.php b/htdocs/admin/emailcollector_card.php index 8dfafb19b63..462839245fa 100644 --- a/htdocs/admin/emailcollector_card.php +++ b/htdocs/admin/emailcollector_card.php @@ -107,6 +107,7 @@ $permissiondellink = $user->admin; // Used by the include of actions_dellink.inc $permissiontoadd = $user->admin; // Used by the include of actions_addupdatedelete.inc.php and actions_lineupdown.inc.php $debuginfo = ''; +$error = 0; /* @@ -121,8 +122,6 @@ if ($reshook < 0) { } if (empty($reshook)) { - $error = 0; - $permissiontoadd = 1; $permissiontodelete = 1; if (empty($backtopage)) { @@ -397,104 +396,112 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea $connectstringsource = ''; $connectstringtarget = ''; - if (function_exists('imap_open')) { - // Note: $object->host has been loaded by the fetch - $usessl = 1; + // Note: $object->host has been loaded by the fetch + $usessl = 1; - $connectstringserver = $object->getConnectStringIMAP($usessl); + $connectstringserver = $object->getConnectStringIMAP($usessl); - if ($action == 'scan') { - if (!empty($conf->global->MAIN_IMAP_USE_PHPIMAP)) { - if ($object->acces_type == 1) { - // Mode OAUth2 with PHP-IMAP - require_once DOL_DOCUMENT_ROOT.'/core/lib/oauth.lib.php'; // define $supportedoauth2array - $keyforsupportedoauth2array = $object->oauth_service; - if (preg_match('/^.*-/', $keyforsupportedoauth2array)) { - $keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array); - } else { - $keyforprovider = ''; - } - $keyforsupportedoauth2array = preg_replace('/-.*$/', '', $keyforsupportedoauth2array); - $keyforsupportedoauth2array = 'OAUTH_'.$keyforsupportedoauth2array.'_NAME'; - - $OAUTH_SERVICENAME = (empty($supportedoauth2array[$keyforsupportedoauth2array]['name']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['name'].($keyforprovider ? '-'.$keyforprovider : '')); - - require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php'; - //$debugtext = "Host: ".$this->host."
Port: ".$this->port."
Login: ".$this->login."
Password: ".$this->password."
access type: ".$this->acces_type."
oauth service: ".$this->oauth_service."
Max email per collect: ".$this->maxemailpercollect; - //dol_syslog($debugtext); - - $storage = new DoliStorage($db, $conf); - - try { - $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME); - $expire = true; - // Is token expired or will token expire in the next 30 seconds - // if (is_object($tokenobj)) { - // $expire = ($tokenobj->getEndOfLife() !== -9002 && $tokenobj->getEndOfLife() !== -9001 && time() > ($tokenobj->getEndOfLife() - 30)); - // } - // Token expired so we refresh it - if (is_object($tokenobj) && $expire) { - $credentials = new Credentials( - getDolGlobalString('OAUTH_'.$object->oauth_service.'_ID'), - getDolGlobalString('OAUTH_'.$object->oauth_service.'_SECRET'), - getDolGlobalString('OAUTH_'.$object->oauth_service.'_URLAUTHORIZE') - ); - $serviceFactory = new \OAuth\ServiceFactory(); - $oauthname = explode('-', $OAUTH_SERVICENAME); - // ex service is Google-Emails we need only the first part Google - $apiService = $serviceFactory->createService($oauthname[0], $credentials, $storage, array()); - // We have to save the token because Google give it only once - $refreshtoken = $tokenobj->getRefreshToken(); - $tokenobj = $apiService->refreshAccessToken($tokenobj); - $tokenobj->setRefreshToken($refreshtoken); - $storage->storeAccessToken($OAUTH_SERVICENAME, $tokenobj); - } - $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME); - if (is_object($tokenobj)) { - $token = $tokenobj->getAccessToken(); - } else { - $object->error = "Token not found"; - return -1; - } - } catch (Exception $e) { - print $e->getMessage(); - } - - $cm = new ClientManager(); - $client = $cm->make([ - 'host' => $object->host, - 'port' => $object->port, - 'encryption' => 'ssl', - 'validate_cert' => true, - 'protocol' => 'imap', - 'username' => $object->login, - 'password' => $token, - 'authentication' => "oauth", - ]); + if ($action == 'scan') { + if (!empty($conf->global->MAIN_IMAP_USE_PHPIMAP)) { + if ($object->acces_type == 1) { + // Mode OAUth2 with PHP-IMAP + require_once DOL_DOCUMENT_ROOT.'/core/lib/oauth.lib.php'; // define $supportedoauth2array + $keyforsupportedoauth2array = $object->oauth_service; + if (preg_match('/^.*-/', $keyforsupportedoauth2array)) { + $keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array); } else { - // Mode login/pass with PHP-IMAP - $cm = new ClientManager(); - $client = $cm->make([ - 'host' => $object->host, - 'port' => $object->port, - 'encryption' => 'ssl', - 'validate_cert' => true, - 'protocol' => 'imap', - 'username' => $object->login, - 'password' => $object->password, - 'authentication' => "login", - ]); + $keyforprovider = ''; } + $keyforsupportedoauth2array = preg_replace('/-.*$/', '', $keyforsupportedoauth2array); + $keyforsupportedoauth2array = 'OAUTH_'.$keyforsupportedoauth2array.'_NAME'; + + $OAUTH_SERVICENAME = (empty($supportedoauth2array[$keyforsupportedoauth2array]['name']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['name'].($keyforprovider ? '-'.$keyforprovider : '')); + + require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php'; + //$debugtext = "Host: ".$this->host."
Port: ".$this->port."
Login: ".$this->login."
Password: ".$this->password."
access type: ".$this->acces_type."
oauth service: ".$this->oauth_service."
Max email per collect: ".$this->maxemailpercollect; + //dol_syslog($debugtext); + + $storage = new DoliStorage($db, $conf, $keyforprovider); + + try { + $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME); + $expire = true; + // Is token expired or will token expire in the next 30 seconds + // if (is_object($tokenobj)) { + // $expire = ($tokenobj->getEndOfLife() !== -9002 && $tokenobj->getEndOfLife() !== -9001 && time() > ($tokenobj->getEndOfLife() - 30)); + // } + // Token expired so we refresh it + if (is_object($tokenobj) && $expire) { + $credentials = new Credentials( + getDolGlobalString('OAUTH_'.$object->oauth_service.'_ID'), + getDolGlobalString('OAUTH_'.$object->oauth_service.'_SECRET'), + getDolGlobalString('OAUTH_'.$object->oauth_service.'_URLAUTHORIZE') + ); + $serviceFactory = new \OAuth\ServiceFactory(); + $oauthname = explode('-', $OAUTH_SERVICENAME); + // ex service is Google-Emails we need only the first part Google + $apiService = $serviceFactory->createService($oauthname[0], $credentials, $storage, array()); + // We have to save the token because Google give it only once + $refreshtoken = $tokenobj->getRefreshToken(); + $tokenobj = $apiService->refreshAccessToken($tokenobj); + $tokenobj->setRefreshToken($refreshtoken); + $storage->storeAccessToken($OAUTH_SERVICENAME, $tokenobj); + } + $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME); + if (is_object($tokenobj)) { + $token = $tokenobj->getAccessToken(); + } else { + $error++; + $morehtml .= "Token not found"; + } + } catch (Exception $e) { + $error++; + $morehtml .= $e->getMessage(); + } + + if (empty($object->login)) { + $error++; + $morehtml .= 'Error: Login is empty. Must be email owner when using MAIN_IMAP_USE_PHPIMAP and OAuth.'; + } + + $cm = new ClientManager(); + $client = $cm->make([ + 'host' => $object->host, + 'port' => $object->port, + 'encryption' => 'ssl', + 'validate_cert' => true, + 'protocol' => 'imap', + 'username' => $object->login, + 'password' => $token, + 'authentication' => "oauth", + ]); + } else { + // Mode login/pass with PHP-IMAP + $cm = new ClientManager(); + $client = $cm->make([ + 'host' => $object->host, + 'port' => $object->port, + 'encryption' => 'ssl', + 'validate_cert' => true, + 'protocol' => 'imap', + 'username' => $object->login, + 'password' => $object->password, + 'authentication' => "login", + ]); + } + if (!$error) { try { $client->connect(); - } catch (ConnectionFailedException $e) { - print $e->getMessage(); - } - $f = $client->getFolders(false, $object->source_directory); - $nbemail = $f[0]->examine()["exists"]; - $morehtml .= $nbemail; - } else { + $f = $client->getFolders(false, $object->source_directory); + $nbemail = $f[0]->examine()["exists"]; + $morehtml .= $nbemail; + } catch (ConnectionFailedException $e) { + $morehtml .= 'ConnectionFailedException '.$e->getMessage(); + } + } + } else { + if (function_exists('imap_open')) { try { if ($sourcedir) { //$connectstringsource = $connectstringserver.imap_utf7_encode($sourcedir); @@ -521,7 +528,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea //dol_syslog("end imap_open connection=".var_export($connection, true)); } catch (Exception $e) { - print $e->getMessage(); + $morehtml .= $e->getMessage(); } if (!$connection) { @@ -540,16 +547,16 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea dol_syslog("Imap close"); imap_close($connection); } + } else { + $morehtml .= 'IMAP functions not available on your PHP. '; } - } else { - $morehtml .= ''.img_picto('', 'refresh', 'class="paddingrightonly"').$langs->trans("Refresh").''; } - - $morehtml .= $form->textwithpicto('', 'connect string '.$connectstringserver); } else { - $morehtml .= 'IMAP functions not available on your PHP. '; + $morehtml .= ''.img_picto('', 'refresh', 'class="paddingrightonly"').$langs->trans("Refresh").''; } + $morehtml .= $form->textwithpicto('', 'connect string '.$connectstringserver); + dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref.'
'.$morehtml.'
', '', 0, '', '', 0, ''); print '
'; diff --git a/htdocs/admin/oauthlogintokens.php b/htdocs/admin/oauthlogintokens.php index d6fcc9e0f7c..5c0ecdb007d 100644 --- a/htdocs/admin/oauthlogintokens.php +++ b/htdocs/admin/oauthlogintokens.php @@ -221,8 +221,11 @@ if ($mode == 'setup' && $user->admin) { $storage = new DoliStorage($db, $conf, $keyforprovider); try { // $OAUTH_SERVICENAME is for example 'Google-keyforprovider' - print $OAUTH_SERVICENAME; + print ''."\n"; $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME); + //print $storage->token.'
'; + //print $tokenobj->getExtraParams()['id_token'].'
'; + //print $tokenobj->getAccessToken().'
'; } catch (Exception $e) { // Return an error if token not found //print $e->getMessage(); @@ -342,7 +345,6 @@ if ($mode == 'setup' && $user->admin) { print '
'; print ''; print '\n"; - // Can edit + // Min amount print '\n"; // SHow counter of validated members publicly diff --git a/htdocs/langs/en_US/members.lang b/htdocs/langs/en_US/members.lang index 71f01b36739..f8981df17d9 100644 --- a/htdocs/langs/en_US/members.lang +++ b/htdocs/langs/en_US/members.lang @@ -206,7 +206,8 @@ SubscriptionsStatistics=Contributions statistics NbOfSubscriptions=Number of contributions AmountOfSubscriptions=Amount collected from contributions TurnoverOrBudget=Turnover (for a company) or Budget (for a foundation) -DefaultAmount=Default amount of contribution +DefaultAmount=Default amount of contribution (used only if no amount is defined at member type level) +MinimumAmount=Minimum amount (used only when contribution amount is free) CanEditAmount=Subscription amount is free CanEditAmountDetail=Visitor can choose/edit amount of its contribution regardless of the member type AmountIsLowerToMinimumNotice=sur un dû total de %s diff --git a/htdocs/public/eventorganization/attendee_new.php b/htdocs/public/eventorganization/attendee_new.php index cb4ba38e628..f245b6c10f7 100644 --- a/htdocs/public/eventorganization/attendee_new.php +++ b/htdocs/public/eventorganization/attendee_new.php @@ -686,7 +686,7 @@ print load_fiche_titre($langs->trans("NewRegistration"), '', '', 0, 0, 'center') print '
'; print '
'; -print '
'; +print '
'; // Welcome message diff --git a/htdocs/public/members/new.php b/htdocs/public/members/new.php index 33bbe59f118..a121f2cc5a1 100644 --- a/htdocs/public/members/new.php +++ b/htdocs/public/members/new.php @@ -30,7 +30,6 @@ * * Note that you can add following constant to change behaviour of page * MEMBER_NEWFORM_AMOUNT Default amount for auto-subscribe form - * MEMBER_NEWFORM_EDITAMOUNT 0 or 1 = Amount can be edited * MEMBER_MIN_AMOUNT Minimum amount * MEMBER_NEWFORM_PAYONLINE Suggest payment with paypal, paybox or stripe * MEMBER_NEWFORM_DOLIBARRTURNOVER Show field turnover (specific for dolibarr foundation) @@ -431,7 +430,7 @@ if (empty($reshook) && $action == 'add') { } if (!empty($conf->global->MEMBER_NEWFORM_PAYONLINE) && $conf->global->MEMBER_NEWFORM_PAYONLINE != '-1') { - if (empty($conf->global->MEMBER_NEWFORM_EDITAMOUNT)) { // If edition of amount not allowed + if (empty($adht->caneditamount)) { // If edition of amount not allowed // TODO Check amount is same than the amount required for the type of member or if not defined as the defeault amount into $conf->global->MEMBER_NEWFORM_AMOUNT // It is not so important because a test is done on return of payment validation. } @@ -505,7 +504,7 @@ print load_fiche_titre($langs->trans("NewSubscription"), '', '', 0, 0, 'center') print '
'; print '
'; -print '
'; +print '
'; if (!empty($conf->global->MEMBER_NEWFORM_TEXT)) { print $langs->trans($conf->global->MEMBER_NEWFORM_TEXT)."
\n"; } else { @@ -765,7 +764,7 @@ if (!empty($conf->global->MEMBER_SKIP_TABLE) || !empty($conf->global->MEMBER_NEW $amount = $conf->global->MEMBER_NEWFORM_AMOUNT; } - if (!empty($conf->global->MEMBER_NEWFORM_EDITAMOUNT) || $caneditamount) { + if ($caneditamount) { print ''; print ' '.$langs->trans("Currency".$conf->currency).' – '; print $amount > 0 ? $langs->trans("AnyAmountWithAdvisedAmount", price($amount, 0, $langs, 1, -1, -1, $conf->currency)): $langs->trans("AnyAmountWithoutAdvisedAmount"); @@ -833,7 +832,7 @@ if (!empty($conf->global->MEMBER_SKIP_TABLE) || !empty($conf->global->MEMBER_NEW $i = 0; while ($i < $num) { - $objp = $db->fetch_object($result); + $objp = $db->fetch_object($result); // Load the member type and information on it print '
'; print ''; @@ -843,7 +842,7 @@ if (!empty($conf->global->MEMBER_SKIP_TABLE) || !empty($conf->global->MEMBER_NEW print ''; print '
'; if (is_object($tokenobj)) { - //var_dump($tokenobj); $tokentoshow = $tokenobj->getAccessToken(); print ''.showValueWithClipboardCPButton($tokentoshow, 1, dol_trunc($tokentoshow, 32)).'
'; //print 'Refresh: '.$tokenobj->getRefreshToken().'
'; diff --git a/htdocs/core/class/CMailFile.class.php b/htdocs/core/class/CMailFile.class.php index a7a87e1ce6e..f6611669556 100644 --- a/htdocs/core/class/CMailFile.class.php +++ b/htdocs/core/class/CMailFile.class.php @@ -908,7 +908,7 @@ class CMailFile require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php'; - $storage = new DoliStorage($db, $conf); + $storage = new DoliStorage($db, $conf, $keyforprovider); try { $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME); $expire = false; @@ -1030,7 +1030,7 @@ class CMailFile require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php'; - $storage = new DoliStorage($db, $conf); + $storage = new DoliStorage($db, $conf, $keyforprovider); try { $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME); diff --git a/htdocs/core/lib/oauth.lib.php b/htdocs/core/lib/oauth.lib.php index 4f504196b47..2074485cd6e 100644 --- a/htdocs/core/lib/oauth.lib.php +++ b/htdocs/core/lib/oauth.lib.php @@ -35,7 +35,7 @@ $supportedoauth2array['OAUTH_GITHUB_NAME'] = array('callbackfile' => 'github', ' if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 2) { $supportedoauth2array['OAUTH_OTHER_NAME'] = array('callbackfile' => 'generic', 'picto' => 'generic', 'urlforapp' => 'OAUTH_OTHER_DESC', 'name'=>'Other', 'urlforcredentials'=>'', 'availablescopes'=>'Standard', 'returnurl'=>'/core/modules/oauth/generic_oauthcallback.php'); // See https://learn.microsoft.com/fr-fr/azure/active-directory/develop/quickstart-register-app#register-an-application - $supportedoauth2array['OAUTH_MICROSOFT_NAME'] = array('callbackfile' => 'microsoft', 'picto' => 'microsoft', 'urlforapp' => 'OAUTH_MICROSOFT_DESC', 'name'=>'Microsoft', 'urlforcredentials'=>'https://portal.azure.com/', 'availablescopes'=>'openid,offline_access,profile,email,IMAP.AccessAsUser.All', 'returnurl'=>'/core/modules/oauth/microsoft_oauthcallback.php'); + $supportedoauth2array['OAUTH_MICROSOFT_NAME'] = array('callbackfile' => 'microsoft', 'picto' => 'microsoft', 'urlforapp' => 'OAUTH_MICROSOFT_DESC', 'name'=>'Microsoft', 'urlforcredentials'=>'https://portal.azure.com/', 'availablescopes'=>'openid,offline_access,profile,email,IMAP.AccessAsUser.All,SMTP.Send,Mail.Read,Mail.Send', 'returnurl'=>'/core/modules/oauth/microsoft_oauthcallback.php'); } diff --git a/htdocs/core/lib/security.lib.php b/htdocs/core/lib/security.lib.php index 51c3434a456..a097ba23e3e 100644 --- a/htdocs/core/lib/security.lib.php +++ b/htdocs/core/lib/security.lib.php @@ -119,6 +119,7 @@ function dolGetRandomBytes($length) function dolEncrypt($chain, $key = '', $ciphering = "AES-256-CTR") { global $dolibarr_main_instance_unique_id; + global $dolibarr_disable_dolcrypt_for_debug; if ($chain === '' || is_null($chain)) { return ''; @@ -136,7 +137,7 @@ function dolEncrypt($chain, $key = '', $ciphering = "AES-256-CTR") $newchain = $chain; - if (function_exists('openssl_encrypt')) { + if (function_exists('openssl_encrypt') && empty($dolibarr_disable_dolcrypt_for_debug)) { $ivlen = 16; if (function_exists('openssl_cipher_iv_length')) { $ivlen = openssl_cipher_iv_length($ciphering); diff --git a/htdocs/core/modules/oauth/google_oauthcallback.php b/htdocs/core/modules/oauth/google_oauthcallback.php index c26187e4475..bd59e513ddf 100644 --- a/htdocs/core/modules/oauth/google_oauthcallback.php +++ b/htdocs/core/modules/oauth/google_oauthcallback.php @@ -217,6 +217,8 @@ if (GETPOST('code')) { // We are coming from oauth provider page. //$url .= 'hd=xxx'; } + //var_dump($url);exit; + // we go on oauth provider authorization page header('Location: '.$url); exit(); diff --git a/htdocs/core/modules/oauth/microsoft_oauthcallback.php b/htdocs/core/modules/oauth/microsoft_oauthcallback.php index ed47eec06e1..4ff573725f3 100644 --- a/htdocs/core/modules/oauth/microsoft_oauthcallback.php +++ b/htdocs/core/modules/oauth/microsoft_oauthcallback.php @@ -155,10 +155,9 @@ if (GETPOST('code') || GETPOST('error')) { // We are coming from oauth provi if (GETPOST('error')) { setEventMessages(GETPOST('error').' '.GETPOST('error_description'), null, 'errors'); } else { - $apiService->tenant = getDolGlobalString($keyforparamtenant); - //$token = $apiService->requestAccessToken(GETPOST('code'), $state); $token = $apiService->requestAccessToken(GETPOST('code')); + //print $token; // Microsoft is a service that does not need state to be stored as second paramater of requestAccessToken setEventMessages($langs->trans('NewTokenStored'), null, 'mesgs'); // Stored into object managed by class DoliStorage so into table oauth_token @@ -182,7 +181,6 @@ if (GETPOST('code') || GETPOST('error')) { // We are coming from oauth provi //if (!preg_match('/^forlogin/', $state)) { // $apiService->setApprouvalPrompt('auto'); //} - $apiService->tenant = getDolGlobalString($keyforparamtenant); // 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). diff --git a/htdocs/core/modules/printing/printgcp.modules.php b/htdocs/core/modules/printing/printgcp.modules.php index c04d3ac9ca5..c1b6ba6c86e 100644 --- a/htdocs/core/modules/printing/printgcp.modules.php +++ b/htdocs/core/modules/printing/printgcp.modules.php @@ -116,10 +116,12 @@ class printing_printgcp extends PrintingDriver 'type'=>'info', ); } else { + $keyforprovider = ''; // @FIXME + $this->google_id = getDolGlobalString('OAUTH_GOOGLE_ID'); $this->google_secret = getDolGlobalString('OAUTH_GOOGLE_SECRET'); // Token storage - $storage = new DoliStorage($this->db, $this->conf); + $storage = new DoliStorage($this->db, $this->conf, $keyforprovider); //$storage->clearToken($this->OAUTH_SERVICENAME_GOOGLE); // Setup the credentials for the requests $credentials = new Credentials( @@ -254,8 +256,11 @@ class printing_printgcp extends PrintingDriver public function getlistAvailablePrinters() { $ret = array(); + + $keyforprovider = ''; // @FIXME + // Token storage - $storage = new DoliStorage($this->db, $this->conf); + $storage = new DoliStorage($this->db, $this->conf, $keyforprovider); // Setup the credentials for the requests $credentials = new Credentials( $this->google_id, @@ -392,8 +397,11 @@ class printing_printgcp extends PrintingDriver 'content' => base64_encode($contents), // encode file content as base64 'contentType' => $contenttype, ); + + $keyforprovider = ''; // @FIXME + // Dolibarr Token storage - $storage = new DoliStorage($this->db, $this->conf); + $storage = new DoliStorage($this->db, $this->conf, $keyforprovider); // Setup the credentials for the requests $credentials = new Credentials( $this->google_id, @@ -441,8 +449,11 @@ class printing_printgcp extends PrintingDriver $error = 0; $html = ''; + + $keyforprovider = ''; // @FIXME + // Token storage - $storage = new DoliStorage($this->db, $this->conf); + $storage = new DoliStorage($this->db, $this->conf, $keyforprovider); // Setup the credentials for the requests $credentials = new Credentials( $this->google_id, diff --git a/htdocs/emailcollector/class/emailcollector.class.php b/htdocs/emailcollector/class/emailcollector.class.php index 2c23f6e0475..2002adafd86 100644 --- a/htdocs/emailcollector/class/emailcollector.class.php +++ b/htdocs/emailcollector/class/emailcollector.class.php @@ -1082,7 +1082,7 @@ class EmailCollector extends CommonObject //$debugtext = "Host: ".$this->host."
Port: ".$this->port."
Login: ".$this->login."
Password: ".$this->password."
access type: ".$this->acces_type."
oauth service: ".$this->oauth_service."
Max email per collect: ".$this->maxemailpercollect; //dol_syslog($debugtext); - $storage = new DoliStorage($db, $conf); + $storage = new DoliStorage($db, $conf, $keyforprovider); try { $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME); diff --git a/htdocs/includes/OAuth/Common/Storage/DoliStorage.php b/htdocs/includes/OAuth/Common/Storage/DoliStorage.php index cf280262e99..60af1f631c6 100644 --- a/htdocs/includes/OAuth/Common/Storage/DoliStorage.php +++ b/htdocs/includes/OAuth/Common/Storage/DoliStorage.php @@ -57,6 +57,8 @@ class DoliStorage implements TokenStorageInterface private $key; //private $stateKey; private $keyforprovider; + public $token; + private $tenant; public $state; public $date_creation; @@ -73,6 +75,7 @@ class DoliStorage implements TokenStorageInterface $this->db = $db; $this->conf = $conf; $this->keyforprovider = $keyforprovider; + $this->token = ''; $this->tokens = array(); $this->states = array(); //$this->key = $key; @@ -96,7 +99,7 @@ class DoliStorage implements TokenStorageInterface /** * {@inheritDoc} */ - public function storeAccessToken($service, TokenInterface $token) + public function storeAccessToken($service, TokenInterface $tokenobj) { global $conf; @@ -104,16 +107,25 @@ class DoliStorage implements TokenStorageInterface //var_dump($token); dol_syslog("storeAccessToken service=".$service); - include_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php'; - $serializedToken = dolEncrypt(serialize($token)); + $servicepluskeyforprovider = $service; + if (!empty($this->keyforprovider)) { + // We clean the keyforprovider after the - to be sure it is not present + $servicepluskeyforprovider = preg_replace('/\-'.preg_quote($this->keyforprovider, '/').'$/', '', $servicepluskeyforprovider); + // Now we add the keyforprovider + $servicepluskeyforprovider .= '-'.$this->keyforprovider; + } - $this->tokens[$service] = $token; + include_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php'; + $serializedToken = serialize($tokenobj); if (!is_array($this->tokens)) { $this->tokens = array(); } + + $this->tokens[$service] = $tokenobj; + $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."oauth_token"; - $sql .= " WHERE service = '".$this->db->escape($service.($this->keyforprovider?'-'.$this->keyforprovider:''))."'"; + $sql .= " WHERE service = '".$this->db->escape($servicepluskeyforprovider)."'"; $sql .= " AND entity IN (".getEntity('oauth_token').")"; $resql = $this->db->query($sql); if (! $resql) { @@ -123,7 +135,7 @@ class DoliStorage implements TokenStorageInterface if ($obj) { // update $sql = "UPDATE ".MAIN_DB_PREFIX."oauth_token"; - $sql.= " SET token = '".$this->db->escape($serializedToken)."'"; + $sql.= " SET token = '".$this->db->escape(dolEncrypt($serializedToken))."'"; $sql.= " WHERE rowid = ".((int) $obj['rowid']); $resql = $this->db->query($sql); if (!$resql) { @@ -132,7 +144,7 @@ class DoliStorage implements TokenStorageInterface } else { // save $sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token (service, token, entity, datec)"; - $sql .= " VALUES ('".$this->db->escape($service.($this->keyforprovider?'-'.$this->keyforprovider:''))."', '".$this->db->escape($serializedToken)."', ".((int) $conf->entity).", "; + $sql .= " VALUES ('".$this->db->escape($servicepluskeyforprovider)."', '".$this->db->escape(dolEncrypt($serializedToken))."', ".((int) $conf->entity).", "; $sql .= " '".$this->db->idate(dol_now())."'"; $sql .= ")"; $resql = $this->db->query($sql); @@ -147,15 +159,26 @@ class DoliStorage implements TokenStorageInterface } /** - * {@inheritDoc} + * Load token and other data from a $service + * Note: Token load are cumulated into array ->tokens when other properties are erased by last loaded token. + * + * @return void */ public function hasAccessToken($service) { // get from db dol_syslog("hasAccessToken service=".$service); + $servicepluskeyforprovider = $service; + if (!empty($this->keyforprovider)) { + // We clean the keyforprovider after the - to be sure it is not present + $servicepluskeyforprovider = preg_replace('/\-'.preg_quote($this->keyforprovider, '/').'$/', '', $servicepluskeyforprovider); + // Now we add the keyforprovider + $servicepluskeyforprovider .= '-'.$this->keyforprovider; + } + $sql = "SELECT token, datec, tms, state FROM ".MAIN_DB_PREFIX."oauth_token"; - $sql .= " WHERE service = '".$this->db->escape($service.(empty($this->keyforprovider) ? '' : '-'.$this->keyforprovider))."'"; + $sql .= " WHERE service = '".$this->db->escape($servicepluskeyforprovider)."'"; $sql .= " AND entity IN (".getEntity('oauth_token').")"; $resql = $this->db->query($sql); if (! $resql) { @@ -164,18 +187,20 @@ class DoliStorage implements TokenStorageInterface $result = $this->db->fetch_array($resql); if ($result) { include_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php'; - $token = unserialize(dolDecrypt($result['token'])); + $tokenobj = unserialize(dolDecrypt($result['token'])); + $this->token = dolDecrypt($result['token']); $this->date_creation = $this->db->jdate($result['datec']); $this->date_modification = $this->db->jdate($result['tms']); $this->state = $result['state']; } else { - $token = ''; + $tokenobj = ''; + $this->token = ''; $this->date_creation = null; $this->date_modification = null; $this->state = ''; } - $this->tokens[$service] = $token; + $this->tokens[$service] = $tokenobj; return is_array($this->tokens) && isset($this->tokens[$service]) @@ -331,4 +356,18 @@ class DoliStorage implements TokenStorageInterface // allow chaining return $this; } + + /** + * Return the token + * + * @return string String for the tenant used to create the token + */ + public function getTenant() + { + // Set/Reset tenant now so it will be defined for. + // TODO We must store it into the table llx_oauth_token + $this->tenant = getDolGlobalString('OAUTH_MICROSOFT'.($this->keyforprovider ? '-'.$this->keyforprovider : '').'_TENANT'); + + return $this->tenant; + } } diff --git a/htdocs/includes/OAuth/OAuth2/Service/Microsoft.php b/htdocs/includes/OAuth/OAuth2/Service/Microsoft.php index b1b6a042c01..e94799b81cf 100644 --- a/htdocs/includes/OAuth/OAuth2/Service/Microsoft.php +++ b/htdocs/includes/OAuth/OAuth2/Service/Microsoft.php @@ -38,9 +38,13 @@ class Microsoft extends AbstractService const SCOPE_APPLICATIONS = 'applications'; const SCOPE_APPLICATIONS_CREATE = 'applications_create'; const SCOPE_IMAP = 'imap'; - const SOCPE_IMAP_AccessAsUser_All='IMAP.AccessAsUser.All'; + const SOCPE_IMAP_ACCESSASUSERALL = 'IMAP.AccessAsUser.All'; + const SOCPE_SMTPSEND = 'SMTP.Send'; + const SOCPE_MAILREAD = 'Mail.Read'; + const SOCPE_MAILSEND = 'Mail.Send'; + + protected $storage; - public string $tenant; /** * MS uses some magical not officialy supported scope to get even moar info like full emailaddresses. @@ -65,6 +69,8 @@ class Microsoft extends AbstractService ) { parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + $this->storage = $storage; + if (null === $baseApiUri) { $this->baseApiUri = new Uri('https://apis.live.net/v5.0/'); } @@ -75,9 +81,11 @@ class Microsoft extends AbstractService */ public function getAuthorizationEndpoint() { - //return new Uri('https://login.live.com/oauth20_authorize.srf'); + $tenant = $this->storage->getTenant(); + + //return new Uri('https://login.live.com/oauth20_authorize.srf'); //return new Uri('https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize'); - return new Uri('https://login.microsoftonline.com/'.$this->tenant.'/oauth2/v2.0/authorize'); + return new Uri('https://login.microsoftonline.com/'.$tenant.'/oauth2/v2.0/authorize'); } /** @@ -85,9 +93,11 @@ class Microsoft extends AbstractService */ public function getAccessTokenEndpoint() { + $tenant = $this->storage->getTenant(); + //return new Uri('https://login.live.com/oauth20_token.srf'); //return new Uri('https://login.microsoftonline.com/organizations/oauth2/v2.0/token'); - return new Uri('https://login.microsoftonline.com/'.$this->tenant.'/oauth2/v2.0/token'); + return new Uri('https://login.microsoftonline.com/'.$tenant.'/oauth2/v2.0/token'); } /** @@ -110,6 +120,7 @@ class Microsoft extends AbstractService } elseif (isset($data['error'])) { throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); } + //print $data['access_token'];exit; $token = new StdOAuth2Token(); $token->setAccessToken($data['access_token']); diff --git a/htdocs/printing/admin/printing.php b/htdocs/printing/admin/printing.php index fb8426abc1b..ed856024d63 100644 --- a/htdocs/printing/admin/printing.php +++ b/htdocs/printing/admin/printing.php @@ -201,13 +201,15 @@ if ($mode == 'setup' && $user->admin) { $i++; if ($key['varname'] == 'PRINTGCP_TOKEN_ACCESS') { + $keyforprovider = ''; // @BUG This must be set + // Token print '
'.$langs->trans("Token").''; $tokenobj = null; // Dolibarr storage - $storage = new DoliStorage($db, $conf); + $storage = new DoliStorage($db, $conf, $keyforprovider); try { $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME_GOOGLE); } catch (Exception $e) { From 4cf21314105bec6a9af63194d7ef0947945318e4 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 8 Feb 2023 01:52:28 +0100 Subject: [PATCH 19/35] Enable MS OAuth --- htdocs/core/lib/oauth.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/lib/oauth.lib.php b/htdocs/core/lib/oauth.lib.php index 2074485cd6e..b2e59a9b87a 100644 --- a/htdocs/core/lib/oauth.lib.php +++ b/htdocs/core/lib/oauth.lib.php @@ -32,10 +32,10 @@ if (isModEnabled('stripe')) { $supportedoauth2array['OAUTH_STRIPE_LIVE_NAME'] = array('callbackfile' => 'stripelive', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeLive', 'urlforcredentials'=>'', 'availablescopes'=>'read_write', 'returnurl'=>'/core/modules/oauth/stripelive_oauthcallback.php'); } $supportedoauth2array['OAUTH_GITHUB_NAME'] = array('callbackfile' => 'github', 'picto' => 'github', 'urlforapp' => 'OAUTH_GITHUB_DESC', 'name'=>'GitHub', 'urlforcredentials'=>'https://github.com/settings/developers', 'availablescopes'=>'user,public_repo', 'returnurl'=>'/core/modules/oauth/github_oauthcallback.php'); +$supportedoauth2array['OAUTH_MICROSOFT_NAME'] = array('callbackfile' => 'microsoft', 'picto' => 'microsoft', 'urlforapp' => 'OAUTH_MICROSOFT_DESC', 'name'=>'Microsoft', 'urlforcredentials'=>'https://portal.azure.com/', 'availablescopes'=>'openid,offline_access,profile,email,IMAP.AccessAsUser.All,SMTP.Send,Mail.Read,Mail.Send', 'returnurl'=>'/core/modules/oauth/microsoft_oauthcallback.php'); if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 2) { $supportedoauth2array['OAUTH_OTHER_NAME'] = array('callbackfile' => 'generic', 'picto' => 'generic', 'urlforapp' => 'OAUTH_OTHER_DESC', 'name'=>'Other', 'urlforcredentials'=>'', 'availablescopes'=>'Standard', 'returnurl'=>'/core/modules/oauth/generic_oauthcallback.php'); // See https://learn.microsoft.com/fr-fr/azure/active-directory/develop/quickstart-register-app#register-an-application - $supportedoauth2array['OAUTH_MICROSOFT_NAME'] = array('callbackfile' => 'microsoft', 'picto' => 'microsoft', 'urlforapp' => 'OAUTH_MICROSOFT_DESC', 'name'=>'Microsoft', 'urlforcredentials'=>'https://portal.azure.com/', 'availablescopes'=>'openid,offline_access,profile,email,IMAP.AccessAsUser.All,SMTP.Send,Mail.Read,Mail.Send', 'returnurl'=>'/core/modules/oauth/microsoft_oauthcallback.php'); } From 47128f2ccc5efe5d3e4d3733c23b9866c7f44127 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 8 Feb 2023 04:40:13 +0100 Subject: [PATCH 20/35] Debug v17 --- htdocs/admin/emailcollector_card.php | 16 ++++++++++++++++ htdocs/core/lib/oauth.lib.php | 2 +- .../modules/oauth/microsoft_oauthcallback.php | 7 ++++++- .../class/emailcollector.class.php | 4 +++- .../OAuth/Common/Http/Client/StreamClient.php | 1 + .../OAuth/OAuth2/Service/AbstractService.php | 2 ++ .../includes/OAuth/OAuth2/Service/Microsoft.php | 5 +++-- htdocs/includes/webklex/php-imap/src/Client.php | 1 + htdocs/langs/en_US/oauth.lang | 2 +- 9 files changed, 34 insertions(+), 6 deletions(-) diff --git a/htdocs/admin/emailcollector_card.php b/htdocs/admin/emailcollector_card.php index 462839245fa..10e7ea300f8 100644 --- a/htdocs/admin/emailcollector_card.php +++ b/htdocs/admin/emailcollector_card.php @@ -421,10 +421,13 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea //$debugtext = "Host: ".$this->host."
Port: ".$this->port."
Login: ".$this->login."
Password: ".$this->password."
access type: ".$this->acces_type."
oauth service: ".$this->oauth_service."
Max email per collect: ".$this->maxemailpercollect; //dol_syslog($debugtext); + $token = ''; + $storage = new DoliStorage($db, $conf, $keyforprovider); try { $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME); + $expire = true; // Is token expired or will token expire in the next 30 seconds // if (is_object($tokenobj)) { @@ -439,11 +442,15 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea ); $serviceFactory = new \OAuth\ServiceFactory(); $oauthname = explode('-', $OAUTH_SERVICENAME); + // ex service is Google-Emails we need only the first part Google $apiService = $serviceFactory->createService($oauthname[0], $credentials, $storage, array()); + // We have to save the token because Google give it only once $refreshtoken = $tokenobj->getRefreshToken(); + //var_dump($tokenobj); $tokenobj = $apiService->refreshAccessToken($tokenobj); + $tokenobj->setRefreshToken($refreshtoken); $storage->storeAccessToken($OAUTH_SERVICENAME, $tokenobj); } @@ -491,6 +498,15 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea } if (!$error) { try { + // To emulate the command connect, you can run + // openssl s_client -crlf -connect outlook.office365.com:993 + // TAG1 AUTHENTICATE XOAUTH2 dXN... + // TO Get debug log, you can set protected $debug = true; in Protocol.php file + // + // A MS bug make this not working ! + // See https://github.com/MicrosoftDocs/office-developer-exchange-docs/issues/100 + // See github.com/MicrosoftDocs/office-developer-exchange-docs/issues/87 + // See github.com/Webklex/php-imap/issues/81 $client->connect(); $f = $client->getFolders(false, $object->source_directory); diff --git a/htdocs/core/lib/oauth.lib.php b/htdocs/core/lib/oauth.lib.php index b2e59a9b87a..8a81d9a80e5 100644 --- a/htdocs/core/lib/oauth.lib.php +++ b/htdocs/core/lib/oauth.lib.php @@ -32,7 +32,7 @@ if (isModEnabled('stripe')) { $supportedoauth2array['OAUTH_STRIPE_LIVE_NAME'] = array('callbackfile' => 'stripelive', 'picto' => 'stripe', 'urlforapp' => '', 'name'=>'StripeLive', 'urlforcredentials'=>'', 'availablescopes'=>'read_write', 'returnurl'=>'/core/modules/oauth/stripelive_oauthcallback.php'); } $supportedoauth2array['OAUTH_GITHUB_NAME'] = array('callbackfile' => 'github', 'picto' => 'github', 'urlforapp' => 'OAUTH_GITHUB_DESC', 'name'=>'GitHub', 'urlforcredentials'=>'https://github.com/settings/developers', 'availablescopes'=>'user,public_repo', 'returnurl'=>'/core/modules/oauth/github_oauthcallback.php'); -$supportedoauth2array['OAUTH_MICROSOFT_NAME'] = array('callbackfile' => 'microsoft', 'picto' => 'microsoft', 'urlforapp' => 'OAUTH_MICROSOFT_DESC', 'name'=>'Microsoft', 'urlforcredentials'=>'https://portal.azure.com/', 'availablescopes'=>'openid,offline_access,profile,email,IMAP.AccessAsUser.All,SMTP.Send,Mail.Read,Mail.Send', 'returnurl'=>'/core/modules/oauth/microsoft_oauthcallback.php'); +$supportedoauth2array['OAUTH_MICROSOFT_NAME'] = array('callbackfile' => 'microsoft', 'picto' => 'microsoft', 'urlforapp' => 'OAUTH_MICROSOFT_DESC', 'name'=>'Microsoft', 'urlforcredentials'=>'https://portal.azure.com/', 'availablescopes'=>'openid,offline_access,profile,email,User.Read,https://outlook.office365.com/IMAP.AccessAsUser.All,https://outlook.office365.com/SMTP.Send', 'returnurl'=>'/core/modules/oauth/microsoft_oauthcallback.php'); if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 2) { $supportedoauth2array['OAUTH_OTHER_NAME'] = array('callbackfile' => 'generic', 'picto' => 'generic', 'urlforapp' => 'OAUTH_OTHER_DESC', 'name'=>'Other', 'urlforcredentials'=>'', 'availablescopes'=>'Standard', 'returnurl'=>'/core/modules/oauth/generic_oauthcallback.php'); // See https://learn.microsoft.com/fr-fr/azure/active-directory/develop/quickstart-register-app#register-an-application diff --git a/htdocs/core/modules/oauth/microsoft_oauthcallback.php b/htdocs/core/modules/oauth/microsoft_oauthcallback.php index 4ff573725f3..bf057676cf3 100644 --- a/htdocs/core/modules/oauth/microsoft_oauthcallback.php +++ b/htdocs/core/modules/oauth/microsoft_oauthcallback.php @@ -155,11 +155,16 @@ if (GETPOST('code') || GETPOST('error')) { // We are coming from oauth provi if (GETPOST('error')) { setEventMessages(GETPOST('error').' '.GETPOST('error_description'), null, 'errors'); } else { + //print GETPOST('code');exit; + //$token = $apiService->requestAccessToken(GETPOST('code'), $state); $token = $apiService->requestAccessToken(GETPOST('code')); - //print $token; // Microsoft is a service that does not need state to be stored as second paramater of requestAccessToken + //print $token->getAccessToken().'

'; + //print $token->getExtraParams()['id_token'].'
'; + //print $token->getRefreshToken().'
';exit; + setEventMessages($langs->trans('NewTokenStored'), null, 'mesgs'); // Stored into object managed by class DoliStorage so into table oauth_token } diff --git a/htdocs/emailcollector/class/emailcollector.class.php b/htdocs/emailcollector/class/emailcollector.class.php index 2002adafd86..0168d8f86c6 100644 --- a/htdocs/emailcollector/class/emailcollector.class.php +++ b/htdocs/emailcollector/class/emailcollector.class.php @@ -1082,10 +1082,13 @@ class EmailCollector extends CommonObject //$debugtext = "Host: ".$this->host."
Port: ".$this->port."
Login: ".$this->login."
Password: ".$this->password."
access type: ".$this->acces_type."
oauth service: ".$this->oauth_service."
Max email per collect: ".$this->maxemailpercollect; //dol_syslog($debugtext); + $token = ''; + $storage = new DoliStorage($db, $conf, $keyforprovider); try { $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME); + $expire = true; // Is token expired or will token expire in the next 30 seconds // if (is_object($tokenobj)) { @@ -1122,7 +1125,6 @@ class EmailCollector extends CommonObject return -1; } - $cm = new ClientManager(); $client = $cm->make([ 'host' => $this->host, diff --git a/htdocs/includes/OAuth/Common/Http/Client/StreamClient.php b/htdocs/includes/OAuth/Common/Http/Client/StreamClient.php index d81fee88202..9849afd4a32 100644 --- a/htdocs/includes/OAuth/Common/Http/Client/StreamClient.php +++ b/htdocs/includes/OAuth/Common/Http/Client/StreamClient.php @@ -57,6 +57,7 @@ class StreamClient extends AbstractClient } $extraHeaders['Content-length'] = 'Content-length: '.strlen($requestBody); + //var_dump($requestBody); var_dump($extraHeaders);var_dump($method);exit; $context = $this->generateStreamContext($requestBody, $extraHeaders, $method); $level = error_reporting(0); diff --git a/htdocs/includes/OAuth/OAuth2/Service/AbstractService.php b/htdocs/includes/OAuth/OAuth2/Service/AbstractService.php index 0de0219306a..b6f89118d83 100644 --- a/htdocs/includes/OAuth/OAuth2/Service/AbstractService.php +++ b/htdocs/includes/OAuth/OAuth2/Service/AbstractService.php @@ -223,6 +223,8 @@ abstract class AbstractService extends BaseAbstractService implements ServiceInt $parameters, $this->getExtraOAuthHeaders() ); + //print $responseBody;exit; // We must have a result "{"token_type":"Bearer","scope... + $token = $this->parseAccessTokenResponse($responseBody); $this->storage->storeAccessToken($this->service(), $token); diff --git a/htdocs/includes/OAuth/OAuth2/Service/Microsoft.php b/htdocs/includes/OAuth/OAuth2/Service/Microsoft.php index e94799b81cf..6c3b18b3c0f 100644 --- a/htdocs/includes/OAuth/OAuth2/Service/Microsoft.php +++ b/htdocs/includes/OAuth/OAuth2/Service/Microsoft.php @@ -38,8 +38,9 @@ class Microsoft extends AbstractService const SCOPE_APPLICATIONS = 'applications'; const SCOPE_APPLICATIONS_CREATE = 'applications_create'; const SCOPE_IMAP = 'imap'; - const SOCPE_IMAP_ACCESSASUSERALL = 'IMAP.AccessAsUser.All'; - const SOCPE_SMTPSEND = 'SMTP.Send'; + const SOCPE_IMAP_ACCESSASUSERALL = 'https://outlook.office365.com/IMAP.AccessAsUser.All'; + const SOCPE_SMTPSEND = 'https://outlook.office365.com/SMTP.Send'; + const SOCPE_USERREAD = 'User.Read'; const SOCPE_MAILREAD = 'Mail.Read'; const SOCPE_MAILSEND = 'Mail.Send'; diff --git a/htdocs/includes/webklex/php-imap/src/Client.php b/htdocs/includes/webklex/php-imap/src/Client.php index 15944e4c646..85c537f16fa 100755 --- a/htdocs/includes/webklex/php-imap/src/Client.php +++ b/htdocs/includes/webklex/php-imap/src/Client.php @@ -353,6 +353,7 @@ class Client { } catch (Exceptions\RuntimeException $e) { throw new ConnectionFailedException("connection setup failed - run exception", 0, $e); } + $this->authenticate(); return $this; diff --git a/htdocs/langs/en_US/oauth.lang b/htdocs/langs/en_US/oauth.lang index e773c470b30..9d4791a9f63 100644 --- a/htdocs/langs/en_US/oauth.lang +++ b/htdocs/langs/en_US/oauth.lang @@ -31,7 +31,7 @@ OAUTH_GITHUB_SECRET=OAuth GitHub Secret OAUTH_URL_FOR_CREDENTIAL=Go to this page to create or get your OAuth ID and Secret OAUTH_STRIPE_TEST_NAME=OAuth Stripe Test OAUTH_STRIPE_LIVE_NAME=OAuth Stripe Live -OAUTH_ID=OAuth ID +OAUTH_ID=OAuth Client ID OAUTH_SECRET=OAuth secret OAUTH_TENANT=OAuth tenant OAuthProviderAdded=OAuth provider added From b880029ac048e3b3e256fac1baf24f2451348a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9=20Cendrier?= Date: Wed, 8 Feb 2023 09:58:30 +0100 Subject: [PATCH 21/35] typos in getAttchments() $arrayobject --- htdocs/emailcollector/class/emailcollector.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/emailcollector/class/emailcollector.class.php b/htdocs/emailcollector/class/emailcollector.class.php index 53889ef9c07..5d0c92b4c32 100644 --- a/htdocs/emailcollector/class/emailcollector.class.php +++ b/htdocs/emailcollector/class/emailcollector.class.php @@ -1951,7 +1951,7 @@ class EmailCollector extends CommonObject 'ticket' => array('table' => 'ticket', 'fields' => array('ref'), 'class' => 'ticket/class/ticket.class.php', - 'object' => ' Ticket'), + 'object' => 'Ticket'), 'knowledgemanagement' => array('table' => 'knowledgemanagement_knowledgerecord', 'fields' => array('ref'), 'class' => 'knowledgemanagement/class/knowledgemanagement.class.php', @@ -1969,7 +1969,7 @@ class EmailCollector extends CommonObject 'class' => 'compta/facture/class/facture.class.php', 'object' => 'Facture'), 'fournisseur/facture' => array('table' => 'facture_fourn', - 'fields' => array('ref', ref_client), + 'fields' => array('ref', 'ref_client'), 'class' => 'fourn/class/fournisseur.facture.class.php', 'object' => 'FactureFournisseur'), 'produit' => array('table' => 'product', From 8a0fd114d45aacda94b9cf803bf4a4649d725928 Mon Sep 17 00:00:00 2001 From: atm-lena Date: Wed, 8 Feb 2023 10:48:46 +0100 Subject: [PATCH 22/35] FIX bug alias extra in extrafields syntax configuration --- htdocs/core/class/extrafields.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/class/extrafields.class.php b/htdocs/core/class/extrafields.class.php index 8ac336f84f8..fbd857b0e27 100644 --- a/htdocs/core/class/extrafields.class.php +++ b/htdocs/core/class/extrafields.class.php @@ -1233,7 +1233,7 @@ class ExtraFields $InfoFieldList[4] = str_replace('$ID$', '0', $InfoFieldList[4]); } //We have to join on extrafield table - if (strpos($InfoFieldList[4], 'extra') !== false) { + if (strpos($InfoFieldList[4], 'extra.') !== false) { $sql .= ' as main, '.$this->db->prefix().$InfoFieldList[0].'_extrafields as extra'; $sqlwhere .= " WHERE extra.fk_object=main.".$InfoFieldList[2]." AND ".$InfoFieldList[4]; } else { From 34b4cad448eae6d66fd8e90744d8c5adcc61d1e6 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 8 Feb 2023 13:18:32 +0100 Subject: [PATCH 23/35] Complete trans --- htdocs/core/modules/modOauth.class.php | 2 +- htdocs/langs/en_US/admin.lang | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/htdocs/core/modules/modOauth.class.php b/htdocs/core/modules/modOauth.class.php index d93a715f1e7..e7c23d30455 100644 --- a/htdocs/core/modules/modOauth.class.php +++ b/htdocs/core/modules/modOauth.class.php @@ -51,7 +51,7 @@ class modOauth extends DolibarrModules // Module label (no space allowed), used if translation string 'ModuleXXXName' not found (where XXX is value of numeric property 'numero' of module) $this->name = preg_replace('/^mod/i', '', get_class($this)); // Module description, used if translation string 'ModuleXXXDesc' not found (where XXX is value of numeric property 'numero' of module) - $this->description = "Enable OAuth authentication"; + $this->description = "Enable OAuth2 authentication"; // Possible values for version are: 'development', 'experimental', 'dolibarr' or 'dolibarr_deprecated' or version $this->version = 'dolibarr'; $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name); diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index 46339a7bf6d..f20065556bf 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -700,6 +700,8 @@ Module62000Name=Incoterms Module62000Desc=Add features to manage Incoterms Module63000Name=Resources Module63000Desc=Manage resources (printers, cars, rooms, ...) for allocating to events +Module66000Name=Enable OAuth2 authentication +Module66000Desc=Provide a tool to generate and manage OAuth2 tokens. The token can then be used by some other modules. Module94160Name=Receptions Permission11=Read customer invoices Permission12=Create/modify customer invoices From cdaeaae047025cf05d7e067a9111c1d100e650f3 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 8 Feb 2023 13:32:15 +0100 Subject: [PATCH 24/35] Fix search on dolistore --- htdocs/admin/modules.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/admin/modules.php b/htdocs/admin/modules.php index dc639a4ecf0..953d8f85a68 100644 --- a/htdocs/admin/modules.php +++ b/htdocs/admin/modules.php @@ -1041,7 +1041,7 @@ if ($mode == 'marketplace') { print '
'; - print '
'; + print ''; ?> From 6cf792438d5bbd35d45655265b67ba462fdc89ba Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 8 Feb 2023 13:48:17 +0100 Subject: [PATCH 25/35] Fix permission --- htdocs/core/lib/invoice.lib.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/lib/invoice.lib.php b/htdocs/core/lib/invoice.lib.php index f4aca8ef9a7..dfe6f78092c 100644 --- a/htdocs/core/lib/invoice.lib.php +++ b/htdocs/core/lib/invoice.lib.php @@ -281,8 +281,8 @@ function getNumberInvoicesPieChart($mode) { global $conf, $db, $langs, $user; - if (($mode == 'customers' && isModEnabled('facture') && !empty($user->rights->facture->lire)) - || ($mode == 'suppliers' && (isModEnabled('fournisseur') || isModEnabled('supplier_invoice')) && !empty($user->rights->facture->lire)) + if (($mode == 'customers' && isModEnabled('facture') && $user->hasRight('facture', 'lire')) + || ($mode == 'suppliers' && (isModEnabled('fournisseur') || isModEnabled('supplier_invoice')) && $user->hasRight('fournisseur', 'facture', 'lire')) ) { include DOL_DOCUMENT_ROOT.'/theme/'.$conf->theme.'/theme_vars.inc.php'; From 7d57cfb767f11fa9307371efccc5114c955fb47f Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 8 Feb 2023 14:04:50 +0100 Subject: [PATCH 26/35] Missing doc --- ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 1b877e2b4b4..189bcd41004 100644 --- a/ChangeLog +++ b/ChangeLog @@ -210,7 +210,7 @@ Following changes may create regressions for some external modules, but were nec * Rename MAIN_LIST_ALLOW_NOTES into MAIN_LIST_HIDE_NOTES and rename MAIN_LIST_ALLOW_PRIVATE_NOTES into MAIN_LIST_HIDE_PRIVATE_NOTES * Rename the substitution for "project label" instead of "project title" in substitution variables * You must use "$objectoffield" to manipulate the current object inside the formulare of computed custom extrafields instead of $obj/$object. - +* Making a global search is sending the parameter using always the name search_all (instead of sometimes sall and search_all) ***** ChangeLog for 16.0.4 compared to 16.0.3 ***** From a17c7aba7348fa1ed3cc03ee974544f7498c9a7e Mon Sep 17 00:00:00 2001 From: Maxime Kohlhaas Date: Wed, 8 Feb 2023 19:25:03 +0100 Subject: [PATCH 27/35] Fix : sall becomes search_all on top menu search also --- htdocs/main.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/main.inc.php b/htdocs/main.inc.php index 1dd9eb35c04..37d018a21ce 100644 --- a/htdocs/main.inc.php +++ b/htdocs/main.inc.php @@ -2732,7 +2732,7 @@ function top_menu_search() $buttonList .= '
'; - $searchInput = ''; + $searchInput = ''; $dropDownHtml = ''; From c871e844699f4874b6ad4a79cbf4a0820a9769b0 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 8 Feb 2023 22:50:27 +0100 Subject: [PATCH 28/35] Fix fatal error on constant no defined --- htdocs/core/modules/modFckeditor.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/modules/modFckeditor.class.php b/htdocs/core/modules/modFckeditor.class.php index fa3a66c37cc..631dba395fe 100644 --- a/htdocs/core/modules/modFckeditor.class.php +++ b/htdocs/core/modules/modFckeditor.class.php @@ -63,7 +63,7 @@ class modFckeditor extends DolibarrModules $this->config_page_url = array("fckeditor.php"); // Dependencies - $this->disabled = in_array(constant('JS_CKEDITOR'), array('disabled', 'disabled/')); + $this->disabled = (defined('JS_CKEDITOR') && in_array(constant('JS_CKEDITOR'), array('disabled', 'disabled/'))); $this->depends = array(); $this->requiredby = array('modWebsites'); From 01e0503b0f073fa39056f168b8fe6cf049e0cd3e Mon Sep 17 00:00:00 2001 From: VESSILLER Date: Thu, 9 Feb 2023 11:52:41 +0100 Subject: [PATCH 29/35] FIX several email sent to the same recipient when adding message from ticket --- htdocs/ticket/class/ticket.class.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/htdocs/ticket/class/ticket.class.php b/htdocs/ticket/class/ticket.class.php index d12df619bb0..122c5ae35ab 100644 --- a/htdocs/ticket/class/ticket.class.php +++ b/htdocs/ticket/class/ticket.class.php @@ -2576,24 +2576,24 @@ class Ticket extends CommonObject $assigned_user = new User($this->db); $assigned_user->fetch($this->fk_user_assign); if (!empty($assigned_user->email)) { - $sendto[] = $assigned_user->getFullName($langs)." <".$assigned_user->email.">"; + $sendto[$assigned_user->email] = $assigned_user->getFullName($langs)." <".$assigned_user->email.">"; } else { $assigned_user_dont_have_email = $assigned_user->getFullName($langs); } } if (empty($sendto)) { if (!empty($conf->global->TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_DEFAULT_EMAIL)) { - $sendto[] = $conf->global->TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_DEFAULT_EMAIL; + $sendto[$conf->global->TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_DEFAULT_EMAIL] = $conf->global->TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_DEFAULT_EMAIL; } elseif (!empty($conf->global->TICKET_NOTIFICATION_EMAIL_TO)) { - $sendto[] = $conf->global->TICKET_NOTIFICATION_EMAIL_TO; + $sendto[$conf->global->TICKET_NOTIFICATION_EMAIL_TO] = $conf->global->TICKET_NOTIFICATION_EMAIL_TO; } } // Add global email address recipient if (!empty($conf->global->TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS) && - !empty($conf->global->TICKET_NOTIFICATION_EMAIL_TO) && !in_array($conf->global->TICKET_NOTIFICATION_EMAIL_TO, $sendto) + !empty($conf->global->TICKET_NOTIFICATION_EMAIL_TO) && !array_key_exists($conf->global->TICKET_NOTIFICATION_EMAIL_TO, $sendto) ) { - $sendto[] = $conf->global->TICKET_NOTIFICATION_EMAIL_TO; + $sendto[$conf->global->TICKET_NOTIFICATION_EMAIL_TO] = $conf->global->TICKET_NOTIFICATION_EMAIL_TO; } if (!empty($sendto)) { @@ -2677,7 +2677,7 @@ class Ticket extends CommonObject if ($info_sendto['email'] != '') { if (!empty($info_sendto['email'])) { - $sendto[] = trim($info_sendto['firstname']." ".$info_sendto['lastname'])." <".$info_sendto['email'].">"; + $sendto[$info_sendto['email']] = trim($info_sendto['firstname']." ".$info_sendto['lastname'])." <".$info_sendto['email'].">"; } //Contact type @@ -2693,9 +2693,9 @@ class Ticket extends CommonObject $message .= '
'.$langs->trans('TicketNotificationEmailBodyInfosTrackUrlinternal').' : '.$object->track_id.'
'; // Add global email address recipient - if ($conf->global->TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS && !in_array($conf->global->TICKET_NOTIFICATION_EMAIL_TO, $sendto)) { + if ($conf->global->TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS && !array_key_exists($conf->global->TICKET_NOTIFICATION_EMAIL_TO, $sendto)) { if (!empty($conf->global->TICKET_NOTIFICATION_EMAIL_TO)) { - $sendto[] = $conf->global->TICKET_NOTIFICATION_EMAIL_TO; + $sendto[$conf->global->TICKET_NOTIFICATION_EMAIL_TO] = $conf->global->TICKET_NOTIFICATION_EMAIL_TO; } } @@ -2755,7 +2755,7 @@ class Ticket extends CommonObject if ($info_sendto['email'] != '' && $info_sendto['email'] != $object->origin_email) { if (!empty($info_sendto['email'])) { - $sendto[] = trim($info_sendto['firstname']." ".$info_sendto['lastname'])." <".$info_sendto['email'].">"; + $sendto[$info_sendto['email']] = trim($info_sendto['firstname']." ".$info_sendto['lastname'])." <".$info_sendto['email'].">"; } $recipient = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname'], '-1').' ('.strtolower($info_sendto['libelle']).')'; @@ -2775,21 +2775,21 @@ class Ticket extends CommonObject $message .= '
'.$message_signature; if (!empty($object->origin_email)) { - $sendto[] = $object->origin_email; + $sendto[$object->origin_email] = $object->origin_email; } - if ($object->fk_soc > 0 && !in_array($object->origin_email, $sendto)) { + if ($object->fk_soc > 0 && !array_key_exists($object->origin_email, $sendto)) { $object->socid = $object->fk_soc; $object->fetch_thirdparty(); if (!empty($object->thirdparty->email)) { - $sendto[] = $object->thirdparty->email; + $sendto[$object->thirdparty->email] = $object->thirdparty->email; } } // altairis: Add global email address reciepient - if ($conf->global->TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS && !in_array($conf->global->TICKET_NOTIFICATION_EMAIL_TO, $sendto)) { + if ($conf->global->TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS && !array_key_exists($conf->global->TICKET_NOTIFICATION_EMAIL_TO, $sendto)) { if (!empty($conf->global->TICKET_NOTIFICATION_EMAIL_TO)) { - $sendto[] = $conf->global->TICKET_NOTIFICATION_EMAIL_TO; + $sendto[$conf->global->TICKET_NOTIFICATION_EMAIL_TO] = $conf->global->TICKET_NOTIFICATION_EMAIL_TO; } } From 45900052e8c8007f88820cc8537ad84520e828cb Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 9 Feb 2023 13:54:39 +0100 Subject: [PATCH 30/35] Debug v17 --- htdocs/adherents/list.php | 2 +- htdocs/public/members/new.php | 18 +++++------------- htdocs/public/project/new.php | 1 - 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/htdocs/adherents/list.php b/htdocs/adherents/list.php index c2b81860210..64cc4fa8dc4 100644 --- a/htdocs/adherents/list.php +++ b/htdocs/adherents/list.php @@ -37,7 +37,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; // Load translation files required by the page -$langs->loadLangs(array("members", "companies")); +$langs->loadLangs(array("members", "companies", "categories")); // Get parameters diff --git a/htdocs/public/members/new.php b/htdocs/public/members/new.php index 7c3e5754b23..33bbe59f118 100644 --- a/htdocs/public/members/new.php +++ b/htdocs/public/members/new.php @@ -34,7 +34,7 @@ * MEMBER_MIN_AMOUNT Minimum amount * MEMBER_NEWFORM_PAYONLINE Suggest payment with paypal, paybox or stripe * MEMBER_NEWFORM_DOLIBARRTURNOVER Show field turnover (specific for dolibarr foundation) - * MEMBER_URL_REDIRECT_SUBSCRIPTION Url to redirect once subscribe submitted + * MEMBER_URL_REDIRECT_SUBSCRIPTION Url to redirect once registration form has been submitted (hidden option, by default we just show a message on same page or redirect to the payment page) * MEMBER_NEWFORM_FORCETYPE Force type of member * MEMBER_NEWFORM_FORCEMORPHY Force nature of member (mor/phy) * MEMBER_NEWFORM_FORCECOUNTRYCODE Force country @@ -73,11 +73,12 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/cunits.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; // Init vars +$backtopage = GETPOST('backtopage', 'alpha'); +$action = GETPOST('action', 'aZ09'); + $errmsg = ''; $num = 0; $error = 0; -$backtopage = GETPOST('backtopage', 'alpha'); -$action = GETPOST('action', 'aZ09'); // Load translation files $langs->loadLangs(array("main", "members", "companies", "install", "other")); @@ -448,15 +449,6 @@ if (empty($reshook) && $action == 'add') { $urlback .= '&entity='.((int) $entity); } } - } - - if (!empty($backtopage)) { - $urlback = $backtopage; - dol_syslog("member ".$adh->ref." was created, we redirect to ".$urlback); - } elseif (!empty($conf->global->MEMBER_URL_REDIRECT_SUBSCRIPTION)) { - $urlback = $conf->global->MEMBER_URL_REDIRECT_SUBSCRIPTION; - // TODO Make replacement of __AMOUNT__, etc... - dol_syslog("member ".$adh->ref." was created, we redirect to ".$urlback); } else { $error++; $errmsg .= join('
', $adh->errors); @@ -476,7 +468,7 @@ if (empty($reshook) && $action == 'add') { } // Action called after a submitted was send and member created successfully -// If MEMBER_URL_REDIRECT_SUBSCRIPTION is set to url we never go here because a redirect was done to this url. +// If MEMBER_URL_REDIRECT_SUBSCRIPTION is set to an url, we never go here because a redirect was done to this url. Same if we ask to redirect to the payment page. // backtopage parameter with an url was set on member submit page, we never go here because a redirect was done to this url. if (empty($reshook) && $action == 'added') { diff --git a/htdocs/public/project/new.php b/htdocs/public/project/new.php index 778fc860e90..108f8d13771 100644 --- a/htdocs/public/project/new.php +++ b/htdocs/public/project/new.php @@ -416,7 +416,6 @@ if (empty($reshook) && $action == 'add') { } // Action called after a submitted was send and member created successfully -// If MEMBER_URL_REDIRECT_SUBSCRIPTION is set to url we never go here because a redirect was done to this url. // backtopage parameter with an url was set on member submit page, we never go here because a redirect was done to this url. if (empty($reshook) && $action == 'added') { llxHeaderVierge($langs->trans("NewLeadForm")); From dfda68a63c495ff54e6610418a33e58ddab53a3b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 9 Feb 2023 14:30:48 +0100 Subject: [PATCH 31/35] Debug v17 - Online membership subscription of members --- htdocs/adherents/admin/website.php | 10 +++---- htdocs/langs/en_US/members.lang | 3 +- .../public/eventorganization/attendee_new.php | 2 +- htdocs/public/members/new.php | 11 ++++---- htdocs/public/partnership/new.php | 2 +- htdocs/public/payment/newpayment.php | 28 +++++++++---------- htdocs/public/payment/paymentok.php | 2 +- htdocs/public/project/new.php | 2 +- htdocs/public/project/suggestbooth.php | 2 +- htdocs/public/project/suggestconference.php | 2 +- 10 files changed, 32 insertions(+), 32 deletions(-) diff --git a/htdocs/adherents/admin/website.php b/htdocs/adherents/admin/website.php index 08e9f617750..705d7cf426f 100644 --- a/htdocs/adherents/admin/website.php +++ b/htdocs/adherents/admin/website.php @@ -58,7 +58,7 @@ if ($action == 'setMEMBER_ENABLE_PUBLIC') { if ($action == 'update') { $public = GETPOST('MEMBER_ENABLE_PUBLIC'); $amount = price2num(GETPOST('MEMBER_NEWFORM_AMOUNT'), 'MT', 2); - $editamount = GETPOST('MEMBER_NEWFORM_EDITAMOUNT'); + $minamount = GETPOST('MEMBER_MIN_AMOUNT'); $publiccounters = GETPOST('MEMBER_COUNTERS_ARE_PUBLIC'); $payonline = GETPOST('MEMBER_NEWFORM_PAYONLINE'); $forcetype = GETPOST('MEMBER_NEWFORM_FORCETYPE', 'int'); @@ -66,7 +66,7 @@ if ($action == 'update') { $res = dolibarr_set_const($db, "MEMBER_ENABLE_PUBLIC", $public, 'chaine', 0, '', $conf->entity); $res = dolibarr_set_const($db, "MEMBER_NEWFORM_AMOUNT", $amount, 'chaine', 0, '', $conf->entity); - $res = dolibarr_set_const($db, "MEMBER_NEWFORM_EDITAMOUNT", $editamount, 'chaine', 0, '', $conf->entity); + $res = dolibarr_set_const($db, "MEMBER_MIN_AMOUNT", $minamount, 'chaine', 0, '', $conf->entity); $res = dolibarr_set_const($db, "MEMBER_COUNTERS_ARE_PUBLIC", $publiccounters, 'chaine', 0, '', $conf->entity); $res = dolibarr_set_const($db, "MEMBER_NEWFORM_PAYONLINE", $payonline, 'chaine', 0, '', $conf->entity); if ($forcetype < 0) { @@ -232,11 +232,11 @@ if (!empty($conf->global->MEMBER_ENABLE_PUBLIC)) { print ''; print "
'; - print $langs->trans("CanEditAmountDetail"); + print $langs->trans("MinimumAmount"); print ''; - print $form->selectyesno("MEMBER_NEWFORM_EDITAMOUNT", (!empty($conf->global->MEMBER_NEWFORM_EDITAMOUNT) ? $conf->global->MEMBER_NEWFORM_EDITAMOUNT : 0), 1); + print ''; print "
'.dol_escape_htmltag($objp->label).''; $displayedamount = max(intval($objp->amount), intval(getDolGlobalInt("MEMBER_MIN_AMOUNT"))); - $caneditamount = !empty($conf->global->MEMBER_NEWFORM_EDITAMOUNT) || $objp->caneditamount; + $caneditamount = $objp->caneditamount; if ($objp->subscription) { if ($displayedamount > 0 || !$caneditamount) { print $displayedamount.' '.strtoupper($conf->currency); diff --git a/htdocs/public/partnership/new.php b/htdocs/public/partnership/new.php index b6444753eb7..d8e65f18c12 100644 --- a/htdocs/public/partnership/new.php +++ b/htdocs/public/partnership/new.php @@ -544,7 +544,7 @@ print load_fiche_titre($langs->trans("NewPartnershipRequest"), '', '', 0, 0, 'ce print '
'; print '
'; -print '
'; +print '
'; if (!empty($conf->global->PARTNERSHIP_NEWFORM_TEXT)) { print $langs->trans($conf->global->PARTNERSHIP_NEWFORM_TEXT)."
\n"; } else { diff --git a/htdocs/public/payment/newpayment.php b/htdocs/public/payment/newpayment.php index 264ead82877..e3257bd659c 100644 --- a/htdocs/public/payment/newpayment.php +++ b/htdocs/public/payment/newpayment.php @@ -1633,7 +1633,7 @@ if ($source == 'member' || $source == 'membersubscription') { $amount = max(0, $conf->global->MEMBER_MIN_AMOUNT, $amount); } print ''.price($amount, 1, $langs, 1, -1, -1, $currency).''; // Price with currency - $caneditamount = !empty($conf->global->MEMBER_NEWFORM_EDITAMOUNT) || $adht->caneditamount; + $caneditamount = $adht->caneditamount; $minimumamount = empty($conf->global->MEMBER_MIN_AMOUNT)? $adht->amount : max($conf->global->MEMBER_MIN_AMOUNT, $adht->amount > $amount); if (!$caneditamount && $minimumamount > $amount) { print ' '. $langs->trans("AmountIsLowerToMinimumNotice", price($adht->amount, 1, $langs, 1, -1, -1, $currency)); @@ -1751,13 +1751,13 @@ if ($source == 'donation') { // Amount print '
'.$langs->trans("Amount"); if (empty($amount)) { - if (empty($conf->global->MEMBER_NEWFORM_AMOUNT)) { + if (empty($conf->global->DONATION_NEWFORM_AMOUNT)) { print ' ('.$langs->trans("ToComplete"); } - if (!empty($conf->global->MEMBER_EXT_URL_SUBSCRIPTION_INFO)) { - print ' - '.$langs->trans("SeeHere").''; + if (!empty($conf->global->DONATION_EXT_URL_SUBSCRIPTION_INFO)) { + print ' - '.$langs->trans("SeeHere").''; } - if (empty($conf->global->MEMBER_NEWFORM_AMOUNT)) { + if (empty($conf->global->DONATION_NEWFORM_AMOUNT)) { print ')'; } } @@ -1767,21 +1767,21 @@ if ($source == 'donation') { $valtoshow = price2num(GETPOST("newamount", 'alpha'), 'MT'); // force default subscription amount to value defined into constant... if (empty($valtoshow)) { - if (!empty($conf->global->MEMBER_NEWFORM_EDITAMOUNT)) { - if (!empty($conf->global->MEMBER_NEWFORM_AMOUNT)) { - $valtoshow = $conf->global->MEMBER_NEWFORM_AMOUNT; + if (!empty($conf->global->DONATION_NEWFORM_EDITAMOUNT)) { + if (!empty($conf->global->DONATION_NEWFORM_AMOUNT)) { + $valtoshow = $conf->global->DONATION_NEWFORM_AMOUNT; } } else { - if (!empty($conf->global->MEMBER_NEWFORM_AMOUNT)) { - $amount = $conf->global->MEMBER_NEWFORM_AMOUNT; + if (!empty($conf->global->DONATION_NEWFORM_AMOUNT)) { + $amount = $conf->global->DONATION_NEWFORM_AMOUNT; } } } } if (empty($amount) || !is_numeric($amount)) { //$valtoshow=price2num(GETPOST("newamount",'alpha'),'MT'); - if (!empty($conf->global->MEMBER_MIN_AMOUNT) && $valtoshow) { - $valtoshow = max($conf->global->MEMBER_MIN_AMOUNT, $valtoshow); + if (!empty($conf->global->DONATION_MIN_AMOUNT) && $valtoshow) { + $valtoshow = max($conf->global->DONATION_MIN_AMOUNT, $valtoshow); } print ''; print ''; @@ -1789,8 +1789,8 @@ if ($source == 'donation') { print ' '.$langs->trans("Currency".$currency).''; } else { $valtoshow = $amount; - if (!empty($conf->global->MEMBER_MIN_AMOUNT) && $valtoshow) { - $valtoshow = max($conf->global->MEMBER_MIN_AMOUNT, $valtoshow); + if (!empty($conf->global->DONATION_MIN_AMOUNT) && $valtoshow) { + $valtoshow = max($conf->global->DONATION_MIN_AMOUNT, $valtoshow); $amount = $valtoshow; } print ''.price($valtoshow, 1, $langs, 1, -1, -1, $currency).''; // Price with currency diff --git a/htdocs/public/payment/paymentok.php b/htdocs/public/payment/paymentok.php index a82364bcce3..e596fd3d261 100644 --- a/htdocs/public/payment/paymentok.php +++ b/htdocs/public/payment/paymentok.php @@ -441,7 +441,7 @@ if ($ispaymentok) { // Do action only if $FinalPaymentAmt is set (session variable is cleaned after this page to avoid duplicate actions when page is POST a second time) if (!empty($FinalPaymentAmt) && $paymentTypeId > 0) { // Security protection: - if (empty($conf->global->MEMBER_NEWFORM_EDITAMOUNT)) { // If we didn't allow members to choose their membership amount (if free amount is allowed, no need to check) + if (empty($adht->caneditamount)) { // If we didn't allow members to choose their membership amount (if the amount is allowed in edit mode, no need to check) if ($object->status == $object::STATUS_DRAFT) { // If the member is not yet validated, we check that the amount is the same as expected. $typeid = $object->typeid; diff --git a/htdocs/public/project/new.php b/htdocs/public/project/new.php index 108f8d13771..c07062e3284 100644 --- a/htdocs/public/project/new.php +++ b/htdocs/public/project/new.php @@ -450,7 +450,7 @@ print load_fiche_titre($langs->trans("NewContact"), '', '', 0, 0, 'center'); print '
'; print '
'; -print '
'; +print '
'; if (!empty($conf->global->PROJECT_NEWFORM_TEXT)) { print $langs->trans($conf->global->PROJECT_NEWFORM_TEXT)."
\n"; } else { diff --git a/htdocs/public/project/suggestbooth.php b/htdocs/public/project/suggestbooth.php index 68e469b6a00..0b2e24716d5 100644 --- a/htdocs/public/project/suggestbooth.php +++ b/htdocs/public/project/suggestbooth.php @@ -564,7 +564,7 @@ print load_fiche_titre($langs->trans("NewSuggestionOfBooth"), '', '', 0, 0, 'cen print '
'; print '
'; -print '
'; +print '
'; dol_htmloutput_errors($errmsg); diff --git a/htdocs/public/project/suggestconference.php b/htdocs/public/project/suggestconference.php index 5590b0bf94c..b3418ec9e84 100644 --- a/htdocs/public/project/suggestconference.php +++ b/htdocs/public/project/suggestconference.php @@ -497,7 +497,7 @@ print load_fiche_titre($langs->trans("NewSuggestionOfConference"), '', '', 0, 0, print '
'; print '
'; -print '
'; +print '
'; dol_htmloutput_errors($errmsg, $errors); From 0e301988a4ed2089bdc3042e77a8b3383708df88 Mon Sep 17 00:00:00 2001 From: "Esteban L. Castro" <43482256+elcf@users.noreply.github.com> Date: Mon, 6 Feb 2023 18:29:50 -0400 Subject: [PATCH 32/35] Added key name to unique constraint --- htdocs/install/mysql/migration/15.0.0-16.0.0.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/install/mysql/migration/15.0.0-16.0.0.sql b/htdocs/install/mysql/migration/15.0.0-16.0.0.sql index d5deec862d0..fd4368b78af 100644 --- a/htdocs/install/mysql/migration/15.0.0-16.0.0.sql +++ b/htdocs/install/mysql/migration/15.0.0-16.0.0.sql @@ -688,7 +688,7 @@ ALTER TABLE llx_actioncomm MODIFY COLUMN note mediumtext; DELETE FROM llx_boxes WHERE box_id IN (select rowid FROM llx_boxes_def WHERE file IN ('box_bom.php@bom', 'box_bom.php', 'box_members.php', 'box_last_modified_ticket', 'box_members_last_subscriptions', 'box_members_last_modified', 'box_members_subscriptions_by_year')); DELETE FROM llx_boxes_def WHERE file IN ('box_bom.php@bom', 'box_bom.php', 'box_members.php', 'box_last_modified_ticket', 'box_members_last_subscriptions', 'box_members_last_modified', 'box_members_subscriptions_by_year'); -ALTER TABLE llx_takepos_floor_tables ADD UNIQUE(entity,label); +ALTER TABLE llx_takepos_floor_tables ADD UNIQUE uk_takepos_floor_tables (entity,label); ALTER TABLE llx_partnership ADD COLUMN url_to_check varchar(255); ALTER TABLE llx_c_partnership_type ADD COLUMN keyword varchar(128); From b65e3e401449c2706de1b5c2945b0838eba73d69 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 9 Feb 2023 15:38:35 +0100 Subject: [PATCH 33/35] Fix all indexes must be named --- htdocs/install/mysql/migration/15.0.0-16.0.0.sql | 2 +- htdocs/install/mysql/tables/llx_takepos_floor_tables.key.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/install/mysql/migration/15.0.0-16.0.0.sql b/htdocs/install/mysql/migration/15.0.0-16.0.0.sql index fd4368b78af..ceff2a32f3c 100644 --- a/htdocs/install/mysql/migration/15.0.0-16.0.0.sql +++ b/htdocs/install/mysql/migration/15.0.0-16.0.0.sql @@ -688,7 +688,7 @@ ALTER TABLE llx_actioncomm MODIFY COLUMN note mediumtext; DELETE FROM llx_boxes WHERE box_id IN (select rowid FROM llx_boxes_def WHERE file IN ('box_bom.php@bom', 'box_bom.php', 'box_members.php', 'box_last_modified_ticket', 'box_members_last_subscriptions', 'box_members_last_modified', 'box_members_subscriptions_by_year')); DELETE FROM llx_boxes_def WHERE file IN ('box_bom.php@bom', 'box_bom.php', 'box_members.php', 'box_last_modified_ticket', 'box_members_last_subscriptions', 'box_members_last_modified', 'box_members_subscriptions_by_year'); -ALTER TABLE llx_takepos_floor_tables ADD UNIQUE uk_takepos_floor_tables (entity,label); +ALTER TABLE llx_takepos_floor_tables ADD UNIQUE INDEX uk_takepos_floor_tables (entity,label); ALTER TABLE llx_partnership ADD COLUMN url_to_check varchar(255); ALTER TABLE llx_c_partnership_type ADD COLUMN keyword varchar(128); diff --git a/htdocs/install/mysql/tables/llx_takepos_floor_tables.key.sql b/htdocs/install/mysql/tables/llx_takepos_floor_tables.key.sql index e90cd67e889..d2f699df325 100644 --- a/htdocs/install/mysql/tables/llx_takepos_floor_tables.key.sql +++ b/htdocs/install/mysql/tables/llx_takepos_floor_tables.key.sql @@ -13,4 +13,4 @@ -- You should have received a copy of the GNU General Public License -- along with this program. If not, see https://www.gnu.org/licenses/. -ALTER TABLE llx_takepos_floor_tables ADD UNIQUE(entity,label); \ No newline at end of file +ALTER TABLE llx_takepos_floor_tables ADD UNIQUE INDEX uk_takepos_floor_tables(entity,label); From 5ae079b0181dfbddf860f48f91161dd64230a61e Mon Sep 17 00:00:00 2001 From: Jean-Patrice Clerc <81645374+jpclerc-beep@users.noreply.github.com> Date: Mon, 6 Feb 2023 21:04:40 +0100 Subject: [PATCH 34/35] Ajout de la ligne pour enregistrer l'email du formulaire --- htdocs/public/project/new.php | 1 + 1 file changed, 1 insertion(+) diff --git a/htdocs/public/project/new.php b/htdocs/public/project/new.php index c07062e3284..d4d6b4b68b5 100644 --- a/htdocs/public/project/new.php +++ b/htdocs/public/project/new.php @@ -224,6 +224,7 @@ if (empty($reshook) && $action == 'add') { } else { $thirdparty->name = dolGetFirstLastname(GETPOST('firstname'), GETPOST('lastname')); } + $thirdparty->email = GETPOST('email'); $thirdparty->address = GETPOST('address'); $thirdparty->zip = GETPOST('zip'); $thirdparty->town = GETPOST('town'); From 04c0272d05c03cb21e288db76c27f4ba2329efab Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 9 Feb 2023 15:58:28 +0100 Subject: [PATCH 35/35] Doc --- htdocs/includes/odtphp/odf.php | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/htdocs/includes/odtphp/odf.php b/htdocs/includes/odtphp/odf.php index 1a951292f94..0bba0e59345 100644 --- a/htdocs/includes/odtphp/odf.php +++ b/htdocs/includes/odtphp/odf.php @@ -24,10 +24,10 @@ class OdfException extends Exception class Odf { protected $config = array( - 'ZIP_PROXY' => 'PclZipProxy', // PclZipProxy, PhpZipProxy - 'DELIMITER_LEFT' => '{', - 'DELIMITER_RIGHT' => '}', - 'PATH_TO_TMP' => '/tmp' + 'ZIP_PROXY' => 'PclZipProxy', // PclZipProxy, PhpZipProxy + 'DELIMITER_LEFT' => '{', + 'DELIMITER_RIGHT' => '}', + 'PATH_TO_TMP' => '/tmp' ); protected $file; protected $contentXml; // To store content of content.xml file @@ -152,12 +152,12 @@ class Odf } /** - * Replaces html tags in odt tags and returns a compatible string + * Replaces html tags found into the $value with ODT compatible tags and return the converted compatible string * - * @param string $value Replacement value - * @param bool $encode If true, special XML characters are encoded - * @param string $charset Charset - * @return string + * @param string $value Replacement value + * @param bool $encode If true, special XML characters are encoded + * @param string $charset Charset + * @return string String in ODTsyntax format */ public function convertVarToOdf($value, $encode = true, $charset = 'ISO-8859') { @@ -203,15 +203,18 @@ class Odf } } $this->contentXml = str_replace('', $fonts . '', $this->contentXml); - } else $convertedValue = preg_replace('/(\r\n|\r|\n)/i', "", $value); + } else { + $convertedValue = preg_replace('/(\r\n|\r|\n)/i', "", $value); + } return $convertedValue; } /** * Replaces html tags in with odt tags and returns an odt string - * @param array $tags An array with html tags generated by the getDataFromHtml() function - * @param array $customStyles An array of style defenitions that should be included inside the odt file + * + * @param array $tags An array with html tags generated by the getDataFromHtml() function + * @param array $customStyles An array of style defenitions that should be included inside the odt file * @param array $fontDeclarations An array of font declarations that should be included inside the odt file * @return string */